refactor(setup): consolidate build/deploy tooling under setup/tools; docs

Move the build and deployment scripts into setup/tools alongside the existing
generators (proxmox-lxc-gen.sh, freeipa-image.sh, generate-modules.sh):
  - setup/generate-answerfile.sh   -> setup/tools/generate-answerfile.sh
  - setup/archiso/build.sh         -> setup/tools/build.sh
  - setup/archiso/wds-deploy.sh    -> setup/tools/wds-deploy.sh
  - setup/archiso/write-usb.sh     -> setup/tools/write-usb.sh

The archiso overlay/ (profile data: airootfs, profiledef.sh, packages.extra)
stays in setup/archiso/; build.sh now resolves it via an OVERLAY_DIR anchored
at the repo root, and DOTFILES_DIR is corrected for the new two-levels-deep
location. Updated every reference (generate-modules.sh, readme.md,
docs/md/{archiso,installation,index}.md).

generate-answerfile.sh: add linux-hardened to the kernel menu; verified its
emitted answerfile schema still matches every field the installers consume
(drive/kernel/keymap/hostname/username/password/luks_password/encrypt/
fido2_*/run_tui/components/desktop_environment/apps/shell_rc/colors).

Docs: add a dedicated ansipa Setup Guide (docs/md/ansipa-setup.md) covering
architecture, the full port list, the nginx portal + reverse-proxy paths, the
exact upstream reverse-proxy snippets (nginx/Caddy), NAT/push-mode behaviour,
and the Arch autofs/AUR caveat; link it from the index and cross-reference the
CheckMK/nginx ports from freeipa-ansible.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUhrcFU8J1Hnf7vNqNxZNi
feat/astal-menu
Amir Alexander Abdelbaki 2026-07-02 13:08:39 +02:00
parent d63e0e554c
commit a73bc7cb93
11 changed files with 271 additions and 21 deletions

230
docs/md/ansipa-setup.md Normal file
View File

@ -0,0 +1,230 @@
# ansipa Setup Guide
**ansipa** = **Ans**ible + Free**IPA**: a GPO-like management plane for Linux
fleets. FreeIPA provides identity, Kerberos, sudo/HBAC and host/user groups;
a lightweight client-side enforcer turns group membership into enforced policy;
CheckMK monitors the fleet; Keycloak adds SSO; and an nginx gateway fronts all
the web UIs behind one hostname.
This guide covers a full deployment: architecture, every port, the reverse-proxy
gateway, the exact snippet to expose it behind your own reverse proxy, the
NAT / behind-firewall behaviour, and known distro caveats. For the policy
catalogue and playbook reference see [FreeIPA & Ansible](freeipa-ansible.md).
---
## Architecture
```
┌─────────────────────────── docker host ───────────────────────────┐
│ docker network ipa-net (172.30.0.0/24) │
your reverse │ │
proxy (TLS) │ ┌── ansipa-nginx (gateway) ──┐ │
│ │ │ / portal start page │ │
https://ansipa ──┼──►│ /ipa/ ─► freeipa :443 ───┼──► 172.30.0.10 FreeIPA │
.example.com │ │ /cmk/ ─► checkmk :5000 ──┼──► 172.30.0.12 CheckMK │
(:443) │ │ /auth/ ─► keycloak :8080 ──┼──► keycloak Keycloak │
│ └────────────────────────────┘ │ │
│ :8088 (http) └─ postgres (KC backend) │
└────────────────────────────────────────────────────────────────────┘
▲ ▲
client-initiated only │ (enroll, kinit, policy pull, │
— works across NAT │ CheckMK push :8000) │
┌────────────────┴───────────┐ ┌──────────────┴─────────────┐
│ enrolled client (Arch) │ │ enrolled client (Fedora…) │
│ ansipa-enforce-policies │ │ SSSD + Kerberos + enforcer │
│ .timer (every 30 min) │ │ │
└─────────────────────────────┘ └────────────────────────────┘
```
The design is **pull-based**: the server never initiates connections to clients.
Every enrolled host runs `ansipa-enforce-policies.sh` on a 30-minute timer, does
`kinit -k host/$FQDN`, reads its own group memberships from FreeIPA, and applies
the matching policies locally. This is what makes the whole system work when
clients are behind NAT (see [Clients behind NAT](#clients-behind-nat)).
---
## Bringing up the server stack
```bash
cd setup/modules/FreeipaAnsible/image
cp .env.example .env && $EDITOR .env # set domain, realm, passwords, ports
# cgroup v2 hosts (WSL2, modern Fedora/Ubuntu/Arch): use the run.sh wrapper —
# the FreeIPA container needs --cgroupns host, which compose cannot express.
./run.sh all # freeipa + postgres + keycloak + checkmk + nginx gateway
# ./run.sh # freeipa only
# ./run.sh down # stop everything
# plain cgroup v1 hosts can use compose directly:
docker compose up -d
docker compose logs -f freeipa # watch first-boot (~10 min)
./keycloak-configure.sh # wire Keycloak → FreeIPA LDAP (once healthy)
```
---
## Ports
### Server (published on the docker host)
| Port | Proto | Service | Notes |
|------|-------|---------|-------|
| 389 | TCP | LDAP | FreeIPA directory |
| 636 | TCP | LDAPS | FreeIPA directory (TLS) |
| 88 | TCP+UDP | Kerberos | KDC |
| 464 | TCP+UDP | kpasswd | password changes |
| 443 | TCP | HTTPS | FreeIPA web UI (also proxied at `/ipa/`) |
| 445 | TCP | SMB | Samba shares (scans, LUKS keys, policy store) |
| 137 | UDP | NetBIOS name | Samba |
| 138 | UDP | NetBIOS datagram | Samba |
| 139 | TCP | NetBIOS session | Samba |
| 8090 | TCP | CheckMK web UI | container :5000 → host 8090 (also proxied at `/cmk/`) |
| 8000 | TCP | CheckMK agent receiver | **push-mode** target for NAT clients |
| 6557 | TCP | CheckMK livestatus | optional, external tooling |
| 8080 | TCP | Keycloak HTTP | dev mode (also proxied at `/auth/`) |
| 8443 | TCP | Keycloak HTTPS | needs a cert for the `start` command |
| **8088** | TCP | **nginx gateway** | single entry for the portal + all UIs (`ANSIPA_HTTP_PORT`) |
### Client (only needed for CheckMK **pull** mode)
| Port | Proto | Service | Notes |
|------|-------|---------|-------|
| 6556 | TCP | check_mk agent socket | server → client; **unreachable across NAT**, use push mode instead |
| 22 | TCP | sshd | if the client is in `dev_daemon-enable-sshd` and you manage over SSH |
---
## The nginx gateway (portal + reverse proxy)
The `ansipa-nginx` service fronts all three web UIs on **one hostname/port** and
serves a portal landing page. Each backend is proxied at the path it already
uses natively, so there is no path rewriting to fight:
| Path | Backend | Notes |
|------|---------|-------|
| `/` | portal start page | static, links to the three UIs |
| `/ipa/` | FreeIPA `:443` | Host + Referer pinned to the IPA hostname (IPA anti-CSRF) |
| `/cmk/` | CheckMK `:5000` | CheckMK already serves under `/cmk` |
| `/auth/` | Keycloak `:8080` | Keycloak runs with `KC_HTTP_RELATIVE_PATH=/auth` |
The gateway speaks plain HTTP on `ANSIPA_HTTP_PORT` (default **8088**) and trusts
`X-Forwarded-*`, so it is designed to sit **behind your own TLS-terminating
reverse proxy**.
### Exposing ansipa behind your main reverse proxy
Point one hostname at the gateway. TLS is terminated by your proxy; ansipa
receives plain HTTP and reads `X-Forwarded-Proto` to build correct URLs.
**nginx** (add to your main reverse proxy):
```nginx
server {
listen 443 ssl;
server_name ansipa.example.com;
# ssl_certificate / ssl_certificate_key ...
location / {
proxy_pass http://DOCKER_HOST_IP:8088;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
}
}
```
**Caddy** (one line):
```caddy
ansipa.example.com {
reverse_proxy DOCKER_HOST_IP:8088
}
```
**Traefik** (docker labels on the nginx service): route the host rule to the
`ansipa-nginx` service on port 80.
### Per-app hostname settings (recommended for production)
Both CheckMK and Keycloak generate some absolute URLs from their own configured
hostname rather than the forwarded one. For a clean deployment behind a public
name, set:
- **Keycloak**: `KC_HOSTNAME=https://ansipa.example.com/auth` in `.env`.
- **CheckMK**: set the site's URL prefix / base URL to your public `https://ansipa.example.com/cmk/` (OMD → *Global settings → User interface → Base URL*), so login redirects use `https` and the public host.
Without these the UIs are still reachable through the proxy; only some redirects
fall back to the container-internal scheme/host.
---
## Clients behind NAT
The controller can live on the public internet while clients sit behind NAT — the
architecture is client-initiated end to end:
| Function | Direction | Works behind NAT? |
|----------|-----------|-------------------|
| Enrollment (`ipa-client-install`) | client → server | ✅ |
| Policy enforcement (30-min timer) | client → server (kinit + `ipa` reads) | ✅ |
| Policy store sync | client → server SMB | ✅ |
| CheckMK **pull** (`:6556`) | **server → client** | ❌ — blocked by NAT |
| CheckMK **push** (`cmk-agent-ctl` → `:8000`) | client → server | ✅ |
So policies and identity Just Work across NAT. For monitoring, NAT clients must
use **push mode**: the server records a push request for each host in
`dev_mon_base` (`ansipa-checkmk-setup.sh` writes `/cmk-creds/push-mode-<host>.req`,
the CheckMK container consumes it and flips the host to `cmk_agent_connection:
push-agent`), and the client registers with `cmk-agent-ctl register --server
<host>:8000`. Advertise a **client-routable** CheckMK URL so agents can reach the
server — set `CMK_ADVERTISED_URL` in `.env` (e.g. `http://mon.example.com:8090`);
it is stored in the `dev_mon_base` group description and must NOT be the
container-internal `172.30.0.12`.
> **Note:** push mode needs the full CheckMK agents `cmk-agent-ctl` binary.
> RPM/DEB distros ship it in the agent package; on Arch the portable
> `check_mk_agent` fallback lacks it, so an Arch NAT client enrolls and gets
> policies but needs `cmk-agent-ctl` installed manually for push monitoring.
---
## Client enrollment
```bash
# on the client, as root:
sudo ./freeipa-enroll.sh --domain ansipa.example.com --realm ANSIPA.EXAMPLE.COM \
--server ipa.ansipa.example.com --hostname client01.ansipa.example.com
# (IPA_PASSWORD env var or --password; --no-dns-update if IPA does not run DNS)
```
Supported distros: **Arch**, **Fedora**, **RHEL/Rocky/AlmaLinux**, **Debian/Ubuntu**.
The script picks the right package manager and package names automatically.
### Arch caveat — autofs / freeipa-client from the AUR
On Arch, `ipa-client-install` comes from the AUR `freeipa-client` package, which
pulls the entire FreeIPA suite plus `autofs` from source — a long build
(tens of minutes on a small VM). ansipa bundles its own `autofs-pkgbuild/` that
builds autofs from upstream git, because the stock AUR `autofs` currently fails
(its pinned `autofs-5.1.9.tar.xz` was pruned from kernel.org). `freeipa-enroll.sh`
builds and installs it automatically before the AUR `freeipa-client` step, so no
manual intervention is needed — just expect the first enrollment to take a while.
---
## Verifying a deployment
```bash
# server
docker compose ps # all healthy
curl -fsS http://DOCKER_HOST_IP:8088/ | grep ansipa # portal
# client (after enrollment + one enforcer run)
id admin # SSSD resolves IPA users
kinit admin && klist # Kerberos works
sudo /usr/local/bin/ansipa-enforce-policies.sh # apply policies now
sudo systemctl list-timers ansipa-enforce-policies.timer
```

View File

@ -16,19 +16,19 @@ sudo pacman -S archiso jq
```bash
# Basic build — interactive installer, no answerfile
bash setup/archiso/build.sh
bash setup/tools/build.sh
# Specify output directory
bash setup/archiso/build.sh /path/to/output
bash setup/tools/build.sh /path/to/output
# Embed an answerfile for automated deployment
bash setup/archiso/build.sh --preconf
bash setup/tools/build.sh --preconf
# Embed a specific answerfile
bash setup/archiso/build.sh --preconf ~/my-server.json
bash setup/tools/build.sh --preconf ~/my-server.json
# Both flags together
bash setup/archiso/build.sh --preconf ~/my-server.json /media/usb/output
bash setup/tools/build.sh --preconf ~/my-server.json /media/usb/output
```
| Flag | Effect |
@ -226,9 +226,9 @@ Every build also produces a `*-netboot-*.tar.gz` artifact alongside the ISO. Thi
### 1. Build with a netboot URL
```bash
bash setup/archiso/build.sh --netboot-url http://your-server/m-archy
bash setup/tools/build.sh --netboot-url http://your-server/m-archy
# or combined with an answerfile:
bash setup/archiso/build.sh --preconf --netboot-url http://your-server/m-archy
bash setup/tools/build.sh --preconf --netboot-url http://your-server/m-archy
```
This generates `~/m-archy-out/m-archy-netboot.ipxe` alongside the netboot tarball.

View File

@ -99,6 +99,14 @@ docker compose up -d freeipa
| 138 | UDP | NetBIOS datagram |
| 8080 | TCP | Keycloak HTTP |
| 8443 | TCP | Keycloak HTTPS |
| 8090 | TCP | CheckMK web UI (container :5000) |
| 8000 | TCP | CheckMK agent receiver (push mode) |
| 6557 | TCP | CheckMK livestatus (optional) |
| 8088 | TCP | nginx gateway — portal + reverse proxy for all UIs |
> For a full deployment walkthrough — the nginx portal, exposing ansipa behind
> your own reverse proxy, and NAT/push-mode behaviour — see the
> [ansipa Setup Guide](ansipa-setup.md).
### Container Internals

View File

@ -16,6 +16,7 @@ A production-grade Arch Linux configuration for network administration, developm
| [Niri](niri.md) | Niri scrollable-tiling compositor — config, keybindings, tools |
| [Modules](modules.md) | Core modules and full optional-app catalogue |
| [Archiso](archiso.md) | Building the custom live installer ISO |
| [ansipa Setup Guide](ansipa-setup.md) | Full deployment: ports, nginx portal + reverse proxy, NAT, enrollment |
| [FreeIPA & Ansible](freeipa-ansible.md) | Identity management and automated config deployment |
| [Editors](editors.md) | Neovim, Micro, Yazi |
| [Utilities](utilities.md) | Encryption helpers, ClamAV, credentials, update scripts |
@ -61,11 +62,17 @@ Dotfiles/
├── update.sh # pacman + yay full system update
├── setup/
│ ├── tui-install.sh # Main interactive / answerfile installer
│ ├── generate-answerfile.sh # Dry-run to produce answerfile.json
│ ├── arch-autoinstall.sh # Automated base OS installer
│ ├── archbaseos-guided-install.sh # Guided base OS installer
│ ├── install-modules.sh # Add optional modules to existing system
│ ├── archiso/ # Custom Arch live ISO builder
│ ├── archiso/overlay/ # Custom Arch live ISO profile overlay
│ ├── tools/ # Build & deploy tooling:
│ │ ├── build.sh # build the live installer ISO + netboot
│ │ ├── wds-deploy.sh # package netboot artifacts for WDS/PXE
│ │ ├── write-usb.sh # write the ISO to a USB stick
│ │ ├── generate-answerfile.sh # dry-run to produce answerfile.json
│ │ ├── proxmox-lxc-gen.sh # Proxmox LXC container generator (TUI)
│ │ └── freeipa-image.sh # build the ansipa FreeIPA container image
│ └── modules/ # Modular install scripts
├── desktopenvs/hyprlua/ # HyprLua (Lua-based Hyprland) configs
├── desktopenvs/niri/ # Niri scrollable-tiling compositor configs

View File

@ -61,7 +61,7 @@ An **answerfile** lets the entire install — base OS _and_ dotfiles — run wit
### Generating an Answerfile
```bash
bash ~/Dotfiles/setup/generate-answerfile.sh [OUTPUT_PATH]
bash ~/Dotfiles/setup/tools/generate-answerfile.sh [OUTPUT_PATH]
# Default output: ~/answerfile.json
```

View File

@ -21,7 +21,7 @@ To add modules to an existing system: `bash ~/Dotfiles/setup/install-modules.sh`
## Cliff Notes
- **Single source of truth for colours** — edit `colors.conf`, run `apply-theme.sh` to propagate everywhere.
- **Answerfile** — generate with `setup/generate-answerfile.sh`, place at `/answerfile.json` for a fully automated install. Passwords are never stored in it.
- **Answerfile** — generate with `setup/tools/generate-answerfile.sh`, place at `/answerfile.json` for a fully automated install. Passwords are never stored in it.
- **Hostname uniqueness** — the MAC address of the primary NIC is appended automatically when an answerfile hostname is set (`myhost` → `myhost-aabbccddee11`).
- **LUKS encryption** — backup key is auto-generated from `/dev/urandom`, enrolled in a second LUKS slot, written to `/_LUKS_BACKUP_KEY` (root-only, inside the encrypted container). Collected by Ansible and stored on the SMB `ansipa-luks-keys` share (KeyAdmin-only read access).
- **Custom ISO**`setup/archiso/` builds a live USB that can embed a pre-baked answerfile for zero-touch deployment. The live environment also includes a **System Reset** mode that reinstalls the root subvolume while preserving home data and FIDO2 auth keys.

View File

@ -41,6 +41,9 @@ set -euo pipefail
# safer when the script is sourced or called with a relative path.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DOTFILES_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
# The archiso overlay (airootfs, profiledef.sh, packages.extra) is profile data
# that lives in setup/archiso/overlay/, not alongside this script in setup/tools/.
OVERLAY_DIR="$DOTFILES_DIR/setup/archiso/overlay"
# ── Argument parsing ───────────────────────────────────────────────────────────
# We support both --flag value and --flag=value syntax.
@ -149,13 +152,13 @@ echo "Applying M-Archy overlay..."
# Merge our custom airootfs overlay ON TOP of the releng copy.
# Files in our overlay replace or extend the releng defaults.
# The trailing /. ensures we copy the directory contents, not the directory itself.
cp -r "$SCRIPT_DIR/overlay/airootfs/." "$PROFILE/airootfs/"
cp -r "$OVERLAY_DIR/airootfs/." "$PROFILE/airootfs/"
echo "Replacing profiledef..."
# profiledef.sh controls ISO metadata (name, label, build modes, boot modes,
# compression settings, and file permissions in the final image).
# We replace the releng default entirely with our customized version.
cp "$SCRIPT_DIR/overlay/profiledef.sh" "$PROFILE/profiledef.sh"
cp "$OVERLAY_DIR/profiledef.sh" "$PROFILE/profiledef.sh"
echo "Adding extra packages..."
# packages.extra lists additional packages beyond releng's defaults.
@ -165,7 +168,7 @@ echo "Adding extra packages..."
while IFS= read -r pkg || [[ -n "$pkg" ]]; do
[[ -z "$pkg" || "$pkg" == \#* ]] && continue
grep -qxF "$pkg" "$PROFILE/packages.x86_64" || echo "$pkg" >> "$PROFILE/packages.x86_64"
done < "$SCRIPT_DIR/overlay/packages.extra"
done < "$OVERLAY_DIR/packages.extra"
# ── Embed installer scripts ────────────────────────────────────────────────────
# These three scripts live in the main setup/ directory of the dotfiles repo

View File

@ -13,7 +13,8 @@
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DOTFILES_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
# This script lives in setup/tools/, so the repo root is two levels up.
DOTFILES_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
OUTPUT="${1:-$HOME/answerfile.json}"
TMP_D="$(mktemp -d)"
trap 'rm -rf "$TMP_D"' EXIT
@ -112,10 +113,11 @@ AF_DRIVE=$(dialog --backtitle "$BACKTITLE" \
# ── Kernel ────────────────────────────────────────────────────────────────────
AF_KERNEL=$(dialog --backtitle "$BACKTITLE" \
--title " Kernel " \
--menu "Select kernel package:" 12 54 3 \
--menu "Select kernel package:" 13 54 4 \
"linux" "Stable kernel" \
"linux-lts" "Long-term support kernel" \
"linux-zen" "Zen performance kernel" \
"linux-hardened" "Security-hardened kernel" \
3>&1 1>&2 2>&3) || AF_KERNEL="linux"
# ── Keymap ────────────────────────────────────────────────────────────────────
@ -451,4 +453,4 @@ mkdir -p "$(dirname "$OUTPUT")"
clear
printf "\n Answerfile saved to: %s\n\n" "$OUTPUT"
printf " To embed in ISO: bash setup/archiso/build.sh --preconf %s\n\n" "$OUTPUT"
printf " To embed in ISO: bash setup/tools/build.sh --preconf %s\n\n" "$OUTPUT"

View File

@ -14,7 +14,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SETUP_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
CONF="$SETUP_DIR/modules.conf"
TUI="$SETUP_DIR/tui-install.sh"
AF="$SETUP_DIR/generate-answerfile.sh"
AF="$SCRIPT_DIR/generate-answerfile.sh" # sibling in setup/tools/
DOCS="$SETUP_DIR/../docs/md/modules.md"
DRY_RUN=false