diff --git a/docs/md/ansipa-setup.md b/docs/md/ansipa-setup.md new file mode 100644 index 0000000..6271fa2 --- /dev/null +++ b/docs/md/ansipa-setup.md @@ -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-.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 +: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 agent’s `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 +``` diff --git a/docs/md/archiso.md b/docs/md/archiso.md index 53f854b..632bbef 100644 --- a/docs/md/archiso.md +++ b/docs/md/archiso.md @@ -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. diff --git a/docs/md/freeipa-ansible.md b/docs/md/freeipa-ansible.md index 76e02a2..d0f5886 100644 --- a/docs/md/freeipa-ansible.md +++ b/docs/md/freeipa-ansible.md @@ -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 diff --git a/docs/md/index.md b/docs/md/index.md index b896238..f292fac 100644 --- a/docs/md/index.md +++ b/docs/md/index.md @@ -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 diff --git a/docs/md/installation.md b/docs/md/installation.md index 7967dad..8eafd2b 100644 --- a/docs/md/installation.md +++ b/docs/md/installation.md @@ -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 ``` diff --git a/readme.md b/readme.md index b387d4e..a75913c 100644 --- a/readme.md +++ b/readme.md @@ -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. diff --git a/setup/archiso/build.sh b/setup/tools/build.sh similarity index 97% rename from setup/archiso/build.sh rename to setup/tools/build.sh index fcdbdde..5bec73f 100755 --- a/setup/archiso/build.sh +++ b/setup/tools/build.sh @@ -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 diff --git a/setup/generate-answerfile.sh b/setup/tools/generate-answerfile.sh similarity index 98% rename from setup/generate-answerfile.sh rename to setup/tools/generate-answerfile.sh index 9f845e0..d952c62 100755 --- a/setup/generate-answerfile.sh +++ b/setup/tools/generate-answerfile.sh @@ -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 \ - "linux" "Stable kernel" \ - "linux-lts" "Long-term support kernel" \ - "linux-zen" "Zen performance kernel" \ + --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" diff --git a/setup/tools/generate-modules.sh b/setup/tools/generate-modules.sh index 5fc587b..bb22c21 100755 --- a/setup/tools/generate-modules.sh +++ b/setup/tools/generate-modules.sh @@ -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 diff --git a/setup/archiso/wds-deploy.sh b/setup/tools/wds-deploy.sh similarity index 100% rename from setup/archiso/wds-deploy.sh rename to setup/tools/wds-deploy.sh diff --git a/setup/archiso/write-usb.sh b/setup/tools/write-usb.sh similarity index 100% rename from setup/archiso/write-usb.sh rename to setup/tools/write-usb.sh