Archived
feat(freeipa): add domain-controller setup — Rocky Linux 9 + FreeIPA 4.13
Installs and documents the FreeIPA identity management server at domain-controller.sweet.home (VMID 108, pve1). Provides Kerberos, LDAP, and integrated DNS for the SWEET.HOME realm. New section: freeipa/ - docs/install.md: full step-by-step reproduction procedure including Proxmox VM prep (Rocky Linux 9 GenericCloud, SeaBIOS, cloud-init), swap setup, static IP, /etc/hosts fix, ipa-server-install flags - docs/pihole-dns.md: how to configure Pi-hole to forward sweet.home queries to the FreeIPA BIND instance - scripts/install.sh: idempotent install script with pre-flight checks; reads passwords from env or interactive prompt (never commits them) - scripts/configure-pihole-dns.sh: idempotent Pi-hole forwarder setup - scripts/verify.sh: read-only health check (13 checks, 0 side effects) - CLAUDE.md: host guardrails for domain-controller Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Jbvxx4xbHVcx1NkK3vtmK
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
# FreeIPA Install Procedure
|
||||
|
||||
Full reproduction guide for `domain-controller.sweet.home`. Tested on
|
||||
Rocky Linux 9.8 (GenericCloud), VMID 108, `pve1.sweet.home`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Proxmox node with `local-zfs` storage and internet access from guests
|
||||
- Rocky Linux 9 GenericCloud image downloaded (see step 1)
|
||||
- SSH access to Proxmox node as a user with `sudo` for `qm`/`pvesm`
|
||||
- The operator's SSH public key available to inject via cloud-init
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Download Rocky Linux 9 GenericCloud image
|
||||
|
||||
On the Proxmox node, download to your ISO/image store:
|
||||
|
||||
```bash
|
||||
wget -O /mnt/pve/server-iso/Rocky-9-GenericCloud.latest.x86_64.qcow2 \
|
||||
https://download.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud.latest.x86_64.qcow2
|
||||
```
|
||||
|
||||
The image is ~617 MB. The Proxmox storage must be configured to accept
|
||||
both ISO images and disk images (set "Content" to include "Disk image"
|
||||
in the Proxmox UI for that storage).
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Prepare the VM in Proxmox
|
||||
|
||||
Use an existing VM or create a new one. The config used for VMID 108:
|
||||
|
||||
- **CPU**: 2 cores, x86-64-v2-AES
|
||||
- **RAM**: 2048 MB
|
||||
- **Disk**: 32 GB on `local-zfs`
|
||||
- **BIOS**: SeaBIOS (GenericCloud uses MBR — not UEFI)
|
||||
- **Network**: virtio on `vmbr0`, firewall enabled
|
||||
- **QEMU guest agent**: enabled
|
||||
|
||||
If rebuilding an existing VM (e.g. replacing a prior OS):
|
||||
|
||||
```bash
|
||||
# On pve1 — stop the VM
|
||||
sudo qm stop <VMID>
|
||||
|
||||
# Remove existing disks from config
|
||||
sudo qm set <VMID> --delete scsi0,efidisk0
|
||||
|
||||
# Switch to SeaBIOS if the VM was UEFI
|
||||
sudo qm set <VMID> --bios seabios
|
||||
|
||||
# Free old disk volumes from storage
|
||||
sudo pvesm free local-zfs:vm-<VMID>-disk-0
|
||||
sudo pvesm free local-zfs:vm-<VMID>-disk-1
|
||||
```
|
||||
|
||||
### Import the Rocky image and configure cloud-init
|
||||
|
||||
```bash
|
||||
# Import image as a new disk
|
||||
sudo qm importdisk <VMID> /mnt/pve/server-iso/Rocky-9-GenericCloud.latest.x86_64.qcow2 local-zfs
|
||||
|
||||
# Check what disk name was assigned
|
||||
sudo qm config <VMID> # look for unused0: local-zfs:vm-<VMID>-disk-N
|
||||
|
||||
# Attach as scsi0 (adjust disk name from above)
|
||||
sudo qm set <VMID> --scsi0 local-zfs:vm-<VMID>-disk-0,iothread=1
|
||||
|
||||
# Resize to 32 GB
|
||||
sudo qm disk resize <VMID> scsi0 32G
|
||||
|
||||
# Add cloud-init drive
|
||||
sudo qm set <VMID> --ide2 local-zfs:cloudinit
|
||||
|
||||
# Write SSH public key to a temp file
|
||||
echo 'ssh-rsa AAAA... wayne@stream' > /tmp/admin-key.pub
|
||||
# (use the key from variables.nix adminSshKey)
|
||||
|
||||
# Configure cloud-init
|
||||
sudo qm set <VMID> \
|
||||
--ciuser wayne \
|
||||
--sshkeys /tmp/admin-key.pub \
|
||||
--ipconfig0 ip=dhcp \
|
||||
--nameserver 192.168.2.253 \
|
||||
--searchdomain sweet.home
|
||||
|
||||
# Set boot order
|
||||
sudo qm set <VMID> --boot order=scsi0
|
||||
|
||||
# Start VM
|
||||
sudo qm start <VMID>
|
||||
```
|
||||
|
||||
### Note on SSH key mismatch
|
||||
|
||||
The GenericCloud image injects the cloud-init SSH key on first boot.
|
||||
If you need to add an additional key (e.g. from a different machine)
|
||||
after first boot, mount the disk via nbd while the VM is stopped:
|
||||
|
||||
```bash
|
||||
sudo qm stop <VMID>
|
||||
sudo qemu-nbd --connect=/dev/nbd1 --format=raw /dev/zvol/rpool/data/vm-<VMID>-disk-0
|
||||
# wait 2s, then:
|
||||
sudo mount /dev/nbd1p4 /mnt/vm # p4 is the root partition on Rocky 9 GenericCloud
|
||||
sudo tee -a /mnt/vm/home/wayne/.ssh/authorized_keys <<< 'ssh-ed25519 AAAA... extra-key'
|
||||
sudo umount /mnt/vm
|
||||
sudo qemu-nbd --disconnect /dev/nbd1
|
||||
sudo qm start <VMID>
|
||||
```
|
||||
|
||||
Rocky 9 GenericCloud partition layout: `p1`=BIOS boot (2M),
|
||||
`p2`=EFI (100M), `p3`=/boot (1G), `p4`=/ (rest).
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — First-boot system preparation
|
||||
|
||||
SSH in as `wayne` once cloud-init has completed (usually 60–90 s):
|
||||
|
||||
```bash
|
||||
ssh wayne@<VM-IP>
|
||||
```
|
||||
|
||||
### Add swap (required — FreeIPA needs headroom beyond 1.7 GB RAM)
|
||||
|
||||
```bash
|
||||
sudo dd if=/dev/zero of=/swapfile bs=1M count=2048 status=progress
|
||||
sudo chmod 600 /swapfile
|
||||
sudo mkswap /swapfile
|
||||
sudo swapon /swapfile
|
||||
echo '/swapfile none swap defaults 0 0' | sudo tee -a /etc/fstab
|
||||
```
|
||||
|
||||
### Set static IP via NetworkManager
|
||||
|
||||
```bash
|
||||
CON=$(nmcli -t -f NAME con show --active | head -1)
|
||||
sudo nmcli con mod "$CON" \
|
||||
ipv4.method manual \
|
||||
ipv4.addresses 192.168.2.138/24 \
|
||||
ipv4.gateway 192.168.2.254 \
|
||||
ipv4.dns 192.168.2.253 \
|
||||
ipv4.dns-search sweet.home
|
||||
sudo nmcli con up "$CON"
|
||||
```
|
||||
|
||||
### Fix /etc/hosts (cloud-init maps FQDN to 127.0.0.1 — IPA requires real IP)
|
||||
|
||||
```bash
|
||||
sudo sed -i '/domain-controller/d' /etc/hosts
|
||||
echo '192.168.2.138 domain-controller.sweet.home domain-controller' \
|
||||
| sudo tee -a /etc/hosts
|
||||
|
||||
# Prevent cloud-init from resetting this on reboot
|
||||
sudo sed -i 's/manage_etc_hosts: true/manage_etc_hosts: false/' \
|
||||
/etc/cloud/cloud.cfg
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — Install FreeIPA packages
|
||||
|
||||
```bash
|
||||
sudo dnf install -y ipa-server ipa-server-dns
|
||||
```
|
||||
|
||||
This pulls ~200 packages including 389-ds, Dogtag PKI, BIND, and MIT
|
||||
Kerberos. Takes 5–10 minutes depending on mirror speed.
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — Run the unattended install
|
||||
|
||||
Generate strong passwords (min 8 chars; store them in your password manager):
|
||||
|
||||
```bash
|
||||
DM_PASS=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 24)
|
||||
ADMIN_PASS=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 24)
|
||||
echo "Directory Manager: $DM_PASS"
|
||||
echo "IPA Admin: $ADMIN_PASS"
|
||||
# Save both in your password manager NOW before proceeding
|
||||
```
|
||||
|
||||
Run the installer (takes 15–20 minutes):
|
||||
|
||||
```bash
|
||||
sudo ipa-server-install \
|
||||
--realm=SWEET.HOME \
|
||||
--domain=sweet.home \
|
||||
--hostname=domain-controller.sweet.home \
|
||||
--ds-password="$DM_PASS" \
|
||||
--admin-password="$ADMIN_PASS" \
|
||||
--setup-dns \
|
||||
--forwarder=192.168.2.253 \
|
||||
--no-dnssec-validation \
|
||||
--no-ntp \
|
||||
--unattended
|
||||
```
|
||||
|
||||
Key flags:
|
||||
- `--setup-dns` — install BIND as IPA's authoritative DNS for `sweet.home`
|
||||
- `--forwarder=192.168.2.253` — forward non-sweet.home queries to Pi-hole
|
||||
- `--no-dnssec-validation` — skip DNSSEC (home lab has no DNSSEC chain)
|
||||
- `--no-ntp` — Proxmox handles time sync for guests; don't install chrony
|
||||
|
||||
---
|
||||
|
||||
## Step 6 — Verify
|
||||
|
||||
```bash
|
||||
# All services should show RUNNING
|
||||
ipactl status
|
||||
|
||||
# Get a Kerberos ticket and confirm
|
||||
echo "$ADMIN_PASS" | kinit admin
|
||||
klist
|
||||
|
||||
# Check DNS SRV records are in place
|
||||
dig +short _kerberos._udp.sweet.home SRV @127.0.0.1
|
||||
# Expected: 0 100 88 domain-controller.sweet.home.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 7 — Configure Pi-hole to forward sweet.home DNS
|
||||
|
||||
See `docs/pihole-dns.md` and `scripts/configure-pihole-dns.sh`.
|
||||
|
||||
The short version — add to Pi-hole's custom dnsmasq config:
|
||||
```
|
||||
server=/sweet.home/192.168.2.138
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 8 — Back up the CA certificate
|
||||
|
||||
```bash
|
||||
# On domain-controller (encrypted with Directory Manager password)
|
||||
ls -lh /root/cacert.p12
|
||||
|
||||
# Copy to a safe location
|
||||
scp root@domain-controller:/root/cacert.p12 ~/backups/ipa-cacert.p12
|
||||
```
|
||||
|
||||
This file is required if you ever set up a replica or need to
|
||||
re-issue service certificates.
|
||||
Reference in New Issue
Block a user