Compare commits

..

5 Commits

Author SHA1 Message Date
Amir Alexander Abdelbaki 6c5c1f8589 gitignore: exclude generated docs/html/ build artifacts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 15:48:47 +02:00
Amir Alexander Abdelbaki 8128ae84e4 docs: add full documentation site with CyberQueer HTML theme
9 Markdown pages covering installation, theming, Hyprland, editors,
modules, archiso, FreeIPA/Ansible, and utilities. md-to-html.sh
converts them to self-contained styled HTML using the live palette
from colors.conf with inline CyberQueer CSS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 15:47:09 +02:00
Amir Alexander Abdelbaki f1ea6dcb54 ansible: add collect-luks-keys playbook for LUKS backup key archival
New playbook collect-luks-keys.yml connects to all enrolled FreeIPA
clients, checks for /_LUKS_BACKUP_KEY (placed there by the installer
when encryption is enabled), and fetches each key to the Ansible
controller as luks-keys/<HOSTNAME>_LUKS_BACKUP_KEY (mode 0400).

Hosts without the file are reported but not treated as errors.
The luks-keys/ store directory is created with mode 0700.

Usage:
  ansible-playbook -i inventory collect-luks-keys.yml

Can be scheduled via cron on the controller for automatic collection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 15:25:05 +02:00
Amir Alexander Abdelbaki b5a3b46c79 setup: add answerfile system for fully automated installs
tui-install.sh:
  - Reads /answerfile.json if present (ANSWERFILE_MODE)
  - All dialog selections (components, DE, apps) sourced from file
  - Hostname from answerfile gets MAC address suffix appended to
    prevent conflicts when deploying one image to multiple machines
  - Interactive hostname inputbox added to the normal TUI flow
  - Colorway dialog added as final step; skipped if no colors differ
    from defaults and no answerfile colors are set
  - Answerfile mode: runs non-interactively, logs warnings on failure

generate-answerfile.sh (new):
  - Dry-runs the full installer dialog flow (OS + dotfiles)
  - Writes selections to ~/answerfile.json (or a given path)
  - No software is installed; passwords are never written to the file

build.sh:
  - New --preconf [FILE] flag embeds an answerfile into the ISO at
    /answerfile.json; omitting the flag leaves the ISO clean
  - Validates JSON with jq if available before embedding
  - Reworked arg parsing to handle the new flag alongside OUT_DIR

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 15:24:47 +02:00
Amir Alexander Abdelbaki e25dd231cb installer: add no-encryption option and auto LUKS backup key
Both arch-autoinstall.sh and archbaseos-guided-install.sh now ask
whether to enable disk encryption. If skipped, btrfs is formatted
directly on the root partition with an appropriate plain GRUB cmdline
(root=UUID=... rootflags=subvol=@).

When encryption is chosen, a 64-byte random key is generated, enrolled
as a second LUKS keyslot, and written to /_LUKS_BACKUP_KEY inside the
new system (mode 400, root-owned, inside the encrypted container).

Also fixes: duplicate 'encrypt' hook in original mkinitcpio HOOKS
strings, missing KERNEL export into arch-autoinstall chroot heredoc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 15:24:29 +02:00
17 changed files with 3210 additions and 330 deletions

1
.gitignore vendored
View File

@ -22,6 +22,7 @@ yazi/*.toml-*
# Generated files
readme.html
docs/html/
# Build/image output artifacts
*.iso

407
docs/md-to-html.sh Normal file
View File

@ -0,0 +1,407 @@
#!/usr/bin/env bash
# md-to-html.sh — Convert Markdown docs to styled HTML with the CyberQueer theme.
#
# Usage:
# bash docs/md-to-html.sh # convert all docs/md/*.md
# bash docs/md-to-html.sh docs/md/foo.md # convert one file
#
# Output lands in docs/html/ with the same base name.
# Requires: python3 with the 'markdown' package (python-markdown on Arch).
# sudo pacman -S python-markdown
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MD_DIR="$SCRIPT_DIR/md"
HTML_DIR="$SCRIPT_DIR/html"
# ── Preflight ─────────────────────────────────────────────────────────────────
if ! python3 -c "import markdown" 2>/dev/null; then
echo "python-markdown not found. Installing..."
sudo pacman -S --noconfirm python-markdown \
|| { echo "Error: please install python-markdown manually."; exit 1; }
fi
mkdir -p "$HTML_DIR"
# ── CyberQueer inline CSS ─────────────────────────────────────────────────────
# Read live palette from colors.conf if available, otherwise use defaults.
_hex() {
local key="$1" default="$2"
if [[ -f "$SCRIPT_DIR/../colors.conf" ]]; then
local v
v=$(grep -m1 "^${key}=" "$SCRIPT_DIR/../colors.conf" 2>/dev/null | cut -d= -f2 | tr -d '[:space:]' | sed 's/#.*//')
[[ -n "$v" ]] && { printf '#%s' "${v^^}"; return; }
fi
printf '#%s' "$default"
}
C_BG=$(_hex COLOR_BG 1A1A1A)
C_TEXT=$(_hex COLOR_TEXT D6ABAB)
C_HI=$(_hex COLOR_HIGHLIGHT E40046)
C_VIO=$(_hex COLOR_DARK 5018DD)
C_RED=$(_hex COLOR_RED F50505)
# Derived
C_BG2="#242424"
C_BG3="#2e2e2e"
C_BORDER="${C_VIO}"
CSS="
/* ── CyberQueer Theme ──────────────────────────────────────────────── */
:root {
--bg: ${C_BG};
--bg2: ${C_BG2};
--bg3: ${C_BG3};
--text: ${C_TEXT};
--accent: ${C_HI};
--violet: ${C_VIO};
--danger: ${C_RED};
--border: ${C_BORDER};
--radius: 10px;
--radius-sm: 5px;
}
/* ── Reset & Base ──────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
background-color: var(--bg);
color: var(--text);
font-family: 'Agave Nerd Font Mono', 'JetBrains Mono', 'Fira Code',
'Cascadia Code', 'Source Code Pro', monospace;
font-size: 15px;
line-height: 1.75;
max-width: 960px;
margin: 0 auto;
padding: 2.5rem 2rem 5rem;
}
/* ── Typography ────────────────────────────────────────────────────── */
h1, h2, h3, h4, h5, h6 {
font-family: 'Agave Nerd Font Mono', monospace;
font-weight: 700;
line-height: 1.25;
margin-top: 2.2rem;
margin-bottom: 0.6rem;
}
h1 {
color: var(--accent);
font-size: 2.2rem;
border-bottom: 3px solid var(--accent);
padding-bottom: 0.4rem;
margin-top: 0;
}
h2 {
color: var(--accent);
font-size: 1.55rem;
border-bottom: 2px solid var(--violet);
padding-bottom: 0.3rem;
}
h3 {
color: var(--violet);
font-size: 1.2rem;
}
h4, h5, h6 {
color: var(--text);
font-size: 1rem;
}
p {
margin: 0.9rem 0;
}
strong { color: var(--accent); font-weight: 700; }
em { color: var(--violet); font-style: italic; }
/* ── Links ─────────────────────────────────────────────────────────── */
a {
color: var(--violet);
text-decoration: none;
border-bottom: 1px solid transparent;
transition: color 0.15s, border-color 0.15s;
}
a:hover {
color: var(--accent);
border-bottom-color: var(--accent);
}
/* ── Code ──────────────────────────────────────────────────────────── */
code {
font-family: inherit;
background: var(--bg2);
color: var(--violet);
border: 1px solid var(--violet);
border-radius: var(--radius-sm);
padding: 0.1em 0.42em;
font-size: 0.9em;
}
pre {
background: var(--bg2);
border: 2px solid var(--violet);
border-radius: var(--radius);
padding: 1.2rem 1.4rem;
overflow-x: auto;
margin: 1.2rem 0;
position: relative;
}
pre code {
background: transparent;
border: none;
padding: 0;
color: var(--text);
font-size: 0.875em;
line-height: 1.6;
}
/* Syntax-like token colouring (no JS required — structural only) */
pre code .kw { color: var(--accent); }
pre code .str { color: var(--violet); }
pre code .cm { color: #666; font-style: italic; }
/* ── Horizontal Rule ───────────────────────────────────────────────── */
hr {
border: none;
border-top: 2px solid var(--violet);
margin: 2rem 0;
opacity: 0.45;
}
/* ── Blockquote ────────────────────────────────────────────────────── */
blockquote {
border-left: 4px solid var(--accent);
background: var(--bg2);
margin: 1.2rem 0;
padding: 0.8rem 1.2rem;
border-radius: 0 var(--radius) var(--radius) 0;
color: var(--text);
opacity: 0.9;
}
blockquote p { margin: 0; }
/* ── Tables ────────────────────────────────────────────────────────── */
table {
width: 100%;
border-collapse: collapse;
border: 2px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
margin: 1.2rem 0;
font-size: 0.9em;
}
th {
background: var(--violet);
color: var(--bg);
text-align: left;
padding: 0.55rem 0.9rem;
font-weight: 700;
letter-spacing: 0.03em;
}
td {
padding: 0.5rem 0.9rem;
border-top: 1px solid var(--bg3);
vertical-align: top;
}
tr:nth-child(even) td { background: var(--bg2); }
tr:hover td { background: var(--bg3); }
/* ── Lists ─────────────────────────────────────────────────────────── */
ul, ol {
padding-left: 1.6rem;
margin: 0.7rem 0;
}
li { margin: 0.25rem 0; }
ul li::marker { color: var(--accent); }
ol li::marker { color: var(--violet); font-weight: 700; }
/* ── Nav sidebar (index page) ──────────────────────────────────────── */
nav ul {
list-style: none;
padding: 0;
}
nav li { margin: 0.3rem 0; }
/* ── Page Header Bar ───────────────────────────────────────────────── */
.page-header {
border-bottom: 3px solid var(--accent);
padding-bottom: 0.6rem;
margin-bottom: 2rem;
display: flex;
align-items: baseline;
gap: 1rem;
}
.page-header .site-title {
color: var(--accent);
font-size: 0.85rem;
opacity: 0.75;
white-space: nowrap;
}
/* ── Back link ─────────────────────────────────────────────────────── */
.back-link {
display: inline-block;
color: var(--violet);
font-size: 0.85rem;
margin-bottom: 1.5rem;
padding: 0.25rem 0.75rem;
border: 1px solid var(--violet);
border-radius: 30px;
transition: background 0.15s, color 0.15s;
}
.back-link:hover {
background: var(--violet);
color: var(--bg);
border-bottom-color: transparent;
}
/* ── Footer ────────────────────────────────────────────────────────── */
footer {
margin-top: 4rem;
padding-top: 1rem;
border-top: 1px solid var(--bg3);
color: var(--text);
opacity: 0.4;
font-size: 0.8rem;
text-align: center;
}
/* ── Responsive ────────────────────────────────────────────────────── */
@media (max-width: 680px) {
body { padding: 1.2rem 1rem 3rem; font-size: 14px; }
h1 { font-size: 1.7rem; }
h2 { font-size: 1.3rem; }
table { display: block; overflow-x: auto; }
}
/* ── Selection ─────────────────────────────────────────────────────── */
::selection {
background: var(--accent);
color: var(--bg);
}
/* ── Scrollbar ─────────────────────────────────────────────────────── */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: var(--bg2); }
::-webkit-scrollbar-thumb { background: var(--violet); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--accent); }
"
# ── HTML template ─────────────────────────────────────────────────────────────
render_html() {
local title="$1"
local body_html="$2"
local is_index="$3" # "true" or "false"
local back_link=""
if [[ "$is_index" == "false" ]]; then
back_link='<a class="back-link" href="index.html">← Index</a>'
fi
cat <<HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title} — M-Archy Dotfiles</title>
<style>${CSS}</style>
</head>
<body>
<header class="page-header">
<span class="site-title">M-Archy Dotfiles</span>
</header>
${back_link}
<main>
${body_html}
</main>
<footer>
Generated by md-to-html.sh &nbsp;·&nbsp; CyberQueer theme &nbsp;·&nbsp; $(date '+%Y-%m-%d')
</footer>
</body>
</html>
HTML
}
# ── Python converter ──────────────────────────────────────────────────────────
convert_file() {
local src="$1"
local base; base="$(basename "$src" .md)"
local dest="$HTML_DIR/${base}.html"
local is_index="false"
[[ "$base" == "index" ]] && is_index="true"
# Extract title from first H1 in the markdown
local title
title=$(grep -m1 '^# ' "$src" | sed 's/^# //' || echo "$base")
# Convert markdown → HTML body via python-markdown
local body_html
body_html=$(python3 - "$src" <<'PYEOF'
import sys, markdown
with open(sys.argv[1], encoding="utf-8") as fh:
text = fh.read()
extensions = [
"tables",
"fenced_code",
"toc",
"attr_list",
"def_list",
"abbr",
"meta",
]
md = markdown.Markdown(extensions=extensions, extension_configs={
"toc": {"permalink": True, "permalink_class": "toc-anchor"},
})
print(md.convert(text))
PYEOF
)
render_html "$title" "$body_html" "$is_index" > "$dest"
printf " %-40s → %s\n" "$(basename "$src")" "$(basename "$dest")"
}
# ── Main ──────────────────────────────────────────────────────────────────────
if [[ $# -gt 0 ]]; then
# Explicit file list
for f in "$@"; do
[[ -f "$f" ]] || { echo "Not found: $f" >&2; continue; }
convert_file "$f"
done
else
# All markdown files in docs/md/
shopt -s nullglob
files=("$MD_DIR"/*.md)
if [[ ${#files[@]} -eq 0 ]]; then
echo "No .md files found in $MD_DIR"
exit 0
fi
echo "Converting ${#files[@]} files → $HTML_DIR/"
for f in "${files[@]}"; do
convert_file "$f"
done
fi
echo
echo "Done. Open docs/html/index.html in a browser."

170
docs/md/archiso.md Normal file
View File

@ -0,0 +1,170 @@
# Archiso — Custom Live Installer
The archiso build system produces a bootable Arch Linux ISO pre-loaded with the M-Archy installer scripts. Optionally, an answerfile can be embedded so the entire install — base OS + dotfiles — runs with zero user interaction.
---
## Prerequisites
```bash
sudo pacman -S archiso jq
```
---
## Building the ISO
```bash
# Basic build — interactive installer, no answerfile
bash setup/archiso/build.sh
# Specify output directory
bash setup/archiso/build.sh /path/to/output
# Embed an answerfile for automated deployment
bash setup/archiso/build.sh --preconf
# Embed a specific answerfile
bash setup/archiso/build.sh --preconf ~/my-server.json
# Both flags together
bash setup/archiso/build.sh --preconf ~/my-server.json /media/usb/output
```
| Flag | Effect |
|------|--------|
| _(none)_ | Clean ISO, no answerfile |
| `--preconf` | Embed `~/answerfile.json` at `/answerfile.json` in the ISO |
| `--preconf FILE` | Embed the specified file instead |
Build artefacts land in `~/m-archy-out/` by default. Override with the `OUT_DIR` environment variable or by passing a path argument.
### Environment Variables
| Variable | Default | Purpose |
|----------|---------|---------|
| `WORK_DIR` | `~/m-archy-build` | Scratch space for mkarchiso |
| `OUT_DIR` | `~/m-archy-out` | ISO output directory |
---
## What the Build Does
1. Copies the upstream `releng` Arch base profile
2. Applies the M-Archy overlay (`setup/archiso/overlay/`)
3. Replaces `profiledef.sh` with the M-Archy version
4. Adds extra packages from `packages.extra`
5. Embeds both installer scripts (`arch-autoinstall.sh`, `archbaseos-guided-install.sh`) into `/root/installer/`
6. If `--preconf`: copies the answerfile to `/answerfile.json` in the ISO's airootfs
7. Runs `mkarchiso` to produce the final `.iso`
---
## Extra Packages on the Live System
Defined in `setup/archiso/overlay/packages.extra`:
```
git
jq
pam-u2f
btop
fastfetch
openssh
```
These are added on top of the standard Arch `releng` package set.
---
## Live System Entry Points
Once booted from the ISO, the following are available:
### `install-arch`
A command placed in `/usr/local/bin/`:
```bash
install-arch # guided mode (default)
install-arch guided # guided interactive install
install-arch auto # automated mode (reads /answerfile.json)
```
### `/root/launch.sh`
Internal dispatcher used by `install-arch`.
### `/answerfile.json`
Only present when built with `--preconf`. Both installer scripts check for this file on startup. If found, all prompts are answered from it — the only interaction required is the disk-encryption password (passwords are never stored in answerfiles).
---
## Automated Deployment Workflow
```
┌─────────────────────────────────────┐
│ Developer machine │
│ │
│ 1. generate-answerfile.sh │
│ → ~/answerfile.json │
│ │
│ 2. build.sh --preconf │
│ → ~/m-archy-out/m-archy.iso │
│ │
│ 3. dd if=m-archy.iso of=/dev/sdX │
└──────────────┬──────────────────────┘
│ USB
┌─────────────────────────────────────┐
│ Target machine (boots from USB) │
│ │
│ 4. install-arch auto │
│ reads /answerfile.json │
│ installs base OS │
│ runs tui-install.sh in chroot │
│ installs dotfiles & apps │
│ │
│ 5. Reboot → ready system │
└─────────────────────────────────────┘
```
For multi-machine deployments, the `hostname` field in the answerfile is combined with the machine's MAC address, so each system gets a unique hostname even though they share the same answerfile.
---
## Overlay Structure
```
setup/archiso/overlay/
├── airootfs/
│ ├── etc/motd # Welcome message
│ ├── root/
│ │ └── launch.sh # Installer entry point
│ └── usr/local/bin/
│ └── install-arch # User-facing CLI command
├── packages.extra # Additional live-system packages
└── profiledef.sh # M-Archy ISO profile definition
```
The `build.sh` script also adds at build time:
```
airootfs/root/installer/
├── arch-autoinstall.sh
└── archbaseos-guided-install.sh
```
---
## Writing the ISO to USB
```bash
# Find the USB drive
lsblk
# Write (replace /dev/sdX with your drive — ALL DATA WILL BE ERASED)
sudo dd if=~/m-archy-out/m-archy-*.iso of=/dev/sdX bs=4M status=progress oflag=sync
```
Or use `ventoy` / `balenaEtcher` as alternatives.

159
docs/md/editors.md Normal file
View File

@ -0,0 +1,159 @@
# Editors
Three editors are configured and deployed by the `shell` component: Neovim (primary), Micro (lightweight), and Yazi (terminal file manager).
---
## Neovim
Config lives in `nvim/`. Deployed to `~/.config/nvim/` during `shell` module install.
### Plugin Manager
[vim-plug](https://github.com/junegunn/vim-plug) is auto-installed on first launch. Run `:PlugInstall` after the first start to fetch all plugins.
### Plugins
#### Language & Completion
| Plugin | Purpose |
|--------|---------|
| `neoclide/coc.nvim` | LSP client, auto-completion, diagnostics |
| `rust-lang/rust.vim` | Rust filetype support |
| `nvim-telescope/telescope.nvim` | Fuzzy finder (files, grep, LSP symbols) |
#### UI
| Plugin | Purpose |
|--------|---------|
| `vim-airline/vim-airline` | Status bar |
| `vim-airline/vim-airline-themes` | Airline theme collection |
| `junegunn/goyo.vim` | Distraction-free writing mode |
| `voldikss/vim-floaterm` | Floating terminal windows |
| `norcalli/nvim-colorizer.lua` | Inline colour preview |
#### Navigation
| Plugin | Purpose |
|--------|---------|
| `preservim/nerdtree` | File tree sidebar |
| `junegunn/fzf` + `fzf.vim` | Fuzzy file/buffer search |
| `elihunter173/dirbuf.nvim` | Editable directory buffer |
#### Snippets
| Plugin | Purpose |
|--------|---------|
| `SirVer/ultisnips` | Snippet engine |
| `honza/vim-snippets` | Snippet collection |
#### Database
| Plugin | Purpose |
|--------|---------|
| `tpope/vim-dadbod` | Database query runner |
| `kristijanhusak/vim-dadbod-ui` | GUI for vim-dadbod |
#### Markdown
| Plugin | Purpose |
|--------|---------|
| `tadmccorkle/markdown.nvim` | Enhanced Markdown support |
| `ellisonleao/glow.nvim` | Markdown preview in terminal |
#### Theme
| Plugin | Purpose |
|--------|---------|
| `the_miro/cyberqueer.nvim` | CyberQueer colour scheme (local) |
### CoC LSP Settings
`nvim/coc-settings.json` configures language servers for:
- Python (pyright)
- Lua (lua-language-server)
- Rust (rust-analyzer via rust.vim)
### CyberQueer Airline Theme
`nvim/cyberqueer-airline.vim` — a custom vim-airline theme using the CyberQueer palette, providing hot-pink/violet segments in the status bar.
---
## Micro
A modern, keyboard-friendly terminal editor. Config in `micro/`. Deployed to `~/.config/micro/`.
### Settings (`settings.json`)
| Setting | Value | Effect |
|---------|-------|--------|
| `colorscheme` | `miro-darcula` | Custom dark colour scheme |
| Various | — | File-type-specific settings |
### Keybindings (`bindings.json`)
Custom keybindings extending Micro's defaults. See `micro/bindings.json` for the full list.
### Colour Schemes
`micro/colorschemes/` contains additional colour scheme definitions including the custom `miro-darcula` scheme.
### Plugins (`micro/plug/`)
| Plugin | Purpose |
|--------|---------|
| `filemanager` | Sidebar file browser |
| `mlsp` | Language Server Protocol integration |
---
## Yazi
A fast, feature-rich terminal file manager written in Rust.
Config in `yazi/`. Deployed to `~/.config/yazi/`.
### Configuration Files
| File | Purpose |
|------|---------|
| `yazi.toml` | Core settings (openers, file rules, display) |
| `theme.toml` | CyberQueer colour palette |
| `keymap.toml` | Keybindings (17 KB, extensively customised) |
### Theme
`yazi/theme.toml` maps the CyberQueer colours to Yazi's theme variables. It is a colour-substitution target in `apply-theme.sh`, so it updates automatically when you change the palette.
### File Openers
| File type | Opens with |
|-----------|-----------|
| `*.svg` | Inkscape |
| Text files | Neovim |
| Everything else | `xdg-open` (system default) |
### Display Options
- Hidden files visible by default
- Symlinks shown and followed
- Three-pane layout (parent, current, preview)
---
## Launching Editors
From the Hyprland desktop:
| Shortcut | Action |
|----------|--------|
| `Super + M` | Open Micro in Kitty |
| `Super + E` | Open Yazi in Kitty |
| `Super + T` then `nvim` | Neovim in terminal |
From the shell:
```bash
nvim file.rs # Neovim
micro config.yaml # Micro
yazi # Yazi file manager
```

211
docs/md/freeipa-ansible.md Normal file
View File

@ -0,0 +1,211 @@
# FreeIPA & Ansible
The FreeIPA/Ansible system provides centralised identity management for a fleet of Arch Linux machines: single sign-on, host-group-driven package and module deployment, LUKS backup key collection, and automatic Keycloak configuration.
All relevant files live under `setup/modules/FreeipaAnsible/`.
---
## Architecture
```
┌────────────────────────────────────┐
│ FreeIPA Server │
│ (can run in Docker / LXC) │
│ │
│ • User/host directory │
│ • Kerberos KDC │
│ • DNS (optional) │
│ • Host group management │
└──────────┬─────────────────────────┘
│ SSSD / Kerberos
┌────────────────────────────────────┐
│ Enrolled client machine │
│ │
│ • sssd — authentication │
│ • ipa CLI — host group queries │
│ • Ansible-deployed timers │
│ ├── package installer │
│ ├── module installer │
│ ├── Flatpak installer │
│ └── baseuser group sync │
└────────────────────────────────────┘
```
---
## FreeIPA Server
### Docker / OCI Image
A pre-built Docker image is available via `setup/modules/FreeipaAnsible/image/`:
```bash
cd setup/modules/FreeipaAnsible/image
cp .env.example .env
# Edit .env with your domain, admin password, realm, etc.
docker compose up -d
```
The container runs `ipa-first-boot.sh` on first start to initialise the IPA instance, then optionally `keycloak-configure.sh` to wire up Keycloak as an OIDC provider.
### Interactive Server Setup
```bash
bash setup/modules/optional-Modules/apps/freeipa-server.sh
```
Prompts for realm, domain, admin password, and whether to generate client-install scripts.
---
## Client Enrollment
### Via Installer Module
Select `freeipa-client` during `tui-install.sh` or `install-modules.sh`.
### Manual Enrollment
Three modes:
```bash
# Answerfile mode (unattended)
bash setup/modules/FreeipaAnsible/freeipa-client.sh \
--answerfile setup/modules/FreeipaAnsible/freeipa-client-answerfile.json
# Interactive prompts
bash setup/modules/FreeipaAnsible/freeipa-client.sh --interactive
# Direct flag passthrough to freeipa-enroll.sh
bash setup/modules/FreeipaAnsible/freeipa-client.sh \
--domain freeipa.example.com \
--server ipa.example.com \
--principal admin
```
### Client Answerfile Schema
```json
{
"domain": "freeipa.abdelbaki.eu",
"realm": "FREEIPA.ABDELBAKI.EU",
"server": "freeipa.abdelbaki.eu",
"hostname": "",
"principal": "admin",
"password": "",
"mkhomedir": true,
"sudo": true,
"dns_update": true,
"ntp_server": "",
"fido2": false,
"fido2_users": []
}
```
Leave `hostname` blank to use the current machine hostname. Leave `password` blank to be prompted at enrollment time.
---
## Ansible Playbooks
All playbooks live in `setup/modules/FreeipaAnsible/ansible/` and require an inventory of enrolled IPA clients.
### Deploy Package Auto-Installer
```bash
ansible-playbook -i inventory deploy-ansipa-install.yml
```
Deploys `ansipa-install-packages.sh` + a systemd timer that runs every 30 minutes. The script queries IPA for host groups named `ansipa-install-<package>` and installs/removes packages to match.
**Group naming convention:** `ansipa-install-firefox` → installs the `firefox` package.
### Deploy Module Auto-Installer
```bash
ansible-playbook -i inventory deploy-ansipa-modules.yml \
[-e ansipa_user=amir]
```
Deploys `ansipa-install-modules.sh` + timer. Queries for groups named `ansipa-module-<name>` and runs the matching script from `/usr/local/lib/ansipa-modules/<name>.sh`.
Module scripts are the same ones used by `install-modules.sh` — copied from `setup/modules/optional-Modules/apps/*.sh`.
**Group naming convention:** `ansipa-module-docker` → runs `docker.sh` on the host.
Each module is applied once and stamped in `/var/lib/ansipa-modules/<name>.done`. Re-running the timer skips already-applied modules.
### Deploy BaseUser Sync
```bash
ansible-playbook -i inventory deploy-baseuser-sync.yml
```
Deploys a `systemd.path` unit that triggers whenever a user logs in. If the user is a member of the IPA `BaseUser` group, they are automatically added to the local `baseusers` group — useful for desktop permission grants.
### Collect LUKS Backup Keys
```bash
ansible-playbook -i inventory collect-luks-keys.yml \
[-e luks_keys_store=/secure/location]
```
For each enrolled host, checks for `/_LUKS_BACKUP_KEY` (placed there by the M-Archy installer when disk encryption is enabled) and fetches it to the controller as:
```
<luks_keys_store>/<HOSTNAME>_LUKS_BACKUP_KEY
```
Keys are stored with mode `0400`. The store directory is created with mode `0700`.
**Schedule for automatic collection:**
```bash
# Add to crontab on the Ansible controller
0 3 * * * cd /path/to/playbooks && ansible-playbook -i inventory collect-luks-keys.yml
```
---
## Host Group Reference
| Group prefix | Handled by | Effect |
|--------------|-----------|--------|
| `ansipa-install-<pkg>` | `ansipa-install-packages.sh` | Install/remove native package |
| `ansipa-module-<name>` | `ansipa-install-modules.sh` | Run module script once |
| `fp_install-<app>` | `ansipa-install-flatpaks.sh` | Install Flatpak app |
| `BaseUser` | `auto-add-baseuser.sh` | Add user to local `baseusers` group |
---
## LUKS Key Flow
```
Install time (arch-autoinstall.sh or archbaseos-guided-install.sh)
─────────────────────────────────────────────────────────────────
1. User sets primary LUKS passphrase interactively
2. 64-byte random key generated from /dev/urandom
3. Key enrolled in second LUKS slot
4. Key written to /_LUKS_BACKUP_KEY (mode 0400, root-only)
inside the encrypted Btrfs volume
Post-install (Ansible)
──────────────────────
5. collect-luks-keys.yml runs from the controller
6. Fetches /_LUKS_BACKUP_KEY from each client
7. Stores as luks-keys/<HOSTNAME>_LUKS_BACKUP_KEY (mode 0400)
on the controller
```
The backup key lives inside the encrypted partition, so it is only accessible when the disk is already unlocked. Its purpose is to allow an admin to unlock the disk for recovery without knowing the user's passphrase.
---
## Auto Enrollment + Ansible
```bash
bash setup/modules/FreeipaAnsible/auto-enroll-ansible.sh
```
Combines FreeIPA client enrollment and Ansible deployment in one shot. Useful for provisioning scripts that run on first boot.

260
docs/md/hyprland.md Normal file
View File

@ -0,0 +1,260 @@
# Hyprland Desktop Environment
The Hyprland setup is the primary desktop environment — a Wayland compositor with a full ecosystem of bars, launchers, notification daemons, and theming tools.
---
## Overview
| Component | Role |
|-----------|------|
| **Hyprland** | Wayland tiling compositor (dwindle layout) |
| **Kitty** | Primary terminal emulator |
| **EWW** | Status bar (three device variants) |
| **Waybar** | Alternative status bar |
| **Wofi** | App launcher (keyboard-driven) |
| **Walker** | Fast CLI launcher |
| **uLauncher** | GUI app launcher |
| **Dunst** | Notification daemon |
| **Hyprlock** | Screen locker |
| **Hyprpaper** | Wallpaper daemon |
| **Hypridle** | Idle management (sleep, lock) |
| **nwg-dock** | Application dock |
| **nwg-drawer** | Application drawer |
| **nwg-panel** | Desktop menu |
| **Vicinae** | Gesture-triggered context launcher |
| **ly** | TUI login manager |
---
## Config File Map
```
desktopenvs/hyprland/
├── hypr/
│ ├── hyprland.conf # Root config — imports all below
│ ├── hyprtoolkit.conf # Toolkit utilities
│ ├── hyprlock.conf # Lock screen
│ ├── hypridle.conf # Idle timeouts
│ └── hyprpaper.conf # Wallpaper
├── hypr-usr/ # Per-user override layer
│ ├── binds.conf # All keybindings
│ ├── input.conf # Keyboard/mouse settings
│ ├── monitors.conf # Display layout
│ ├── windowrules.conf # Per-app behaviour rules
│ └── autostart.conf # Startup programs
├── kitty/
│ ├── kitty.conf # Terminal config
│ ├── current-theme.conf # Active palette (CyberQueer)
│ └── themes/cyberqueer.conf # Theme definition
├── waybar/
│ ├── config # Module layout and data sources
│ └── style.css # CyberQueer styling
├── wofi/style.css # Launcher styling
├── walker/themes/ # Walker theme (cyberqueer.css)
├── dunst/ # Notification styling
├── eww/ # EWW bar (PC — no battery)
├── eww-nobattery/ # EWW bar alias
├── eww-touch/ # EWW bar (tablet / touchscreen)
├── spicetify/ # Spotify CyberQueer themes
├── Vencord/ # Discord CyberQueer themes
├── btop/ # System monitor + cyberqueer.theme
├── scripts/ # 20+ utility scripts
└── config-updater/ # Config synchronisation tool
```
---
## Keybindings
All bindings live in `hypr-usr/binds.conf`.
### Applications
| Binding | Action |
|---------|--------|
| `Super + T` | Kitty terminal |
| `Super + Shift + T` | Cool Retro Term (CRT profile) |
| `Super + M` | Micro editor |
| `Super + E` | Thunar file manager |
| `Super + X` | Wofi app launcher |
| `Super + F` | File search (wofi) |
| `Super + Shift + F` | Folder search (wofi) |
### Window Management
| Binding | Action |
|---------|--------|
| `Super + Q` | Close focused window |
| `Super + V` | Toggle floating |
| `Super + Shift + V` | Centre floating window |
| `Super + P` | Toggle pseudo-tiling |
| `Super + J` | Toggle split direction |
| `Super + Arrow / hjkl` | Focus window by direction |
| `Super + Shift + Arrow / hjkl` | Move window by direction |
| `Super + [0-9]` | Switch workspace |
| `Super + Shift + [0-9]` | Move window to workspace |
| `Super + mouse drag` | Move/resize floating window |
### System
| Binding | Action |
|---------|--------|
| `Super + O` | Lock screen (hyprlock) |
| `Super + Alt + O` | Power menu |
| `Super + Ctrl + O` | Shutdown immediately |
| `Super + Z` | Toggle EWW bar |
| `Super + Ctrl + P` | Start screen recording |
### Touchpad Gestures
| Gesture | Action |
|---------|--------|
| 3-finger swipe left/right | Switch workspace |
| 3-finger swipe up/down | Move window to workspace |
| 3-finger pinch | Toggle overview |
| 4-finger tap | Open launcher |
---
## Status Bar (EWW)
EWW (Elkowar's Wayland Window Manager) is compiled from source during Hyprland install and comes in three variants:
| Variant | Device | Battery Widget |
|---------|--------|---------------|
| `eww/` | Desktop PC | No |
| `eww-nobattery/` | Desktop PC | No |
| `eww-touch/` | Laptop / tablet | Yes |
During install the EWW installer asks which variant to use.
### Waybar
An alternative to EWW — always installed. The bar layout:
- **Left**: clock, disk usage, RAM, CPU, temperature
- **Centre**: workspace switcher, active window title
- **Right**: network speed, IP address, system tray, audio volume, battery (if present)
---
## Launchers
### Wofi
Keyboard-driven app/file launcher. Activated with `Super + X`.
Config: `wofi/style.css` — themed with CyberQueer colours.
### Walker
Fast CLI launcher (`walker`). Pre-configured with `walker/config.toml` and the `cyberqueer.css` / `cyberqueer.toml` theme files.
### uLauncher
GUI launcher with plugin support. Theme: `ulauncher/user-themes/cyberqueer/`.
Colours in the theme are colour-substitution targets (updated by `apply-theme.sh`).
---
## Notification Daemon (Dunst)
Lightweight notification daemon. Config in `dunst/dunstrc`.
Notifications appear top-right with CyberQueer styling.
---
## Lock Screen (Hyprlock)
`hyprlock` is the Wayland screen locker. Config at `hypr/hyprlock.conf`.
Activated by `Super + O` or automatically via `hypridle` after idle timeout.
---
## Scripts Reference
All scripts live in `desktopenvs/hyprland/scripts/` and are deployed to `~/.config/scripts/`.
| Script | Purpose |
|--------|---------|
| `caffeine.sh` | Toggle hypridle (prevent sleep) |
| `ewwstart.sh` | Launch EWW bar on startup |
| `ewwstart-niri.sh` | EWW for niri compositor variant |
| `togglebar.sh` | Show/hide EWW bar (monitor-aware) |
| `helpmenu.sh` | Display keybindings from binds.conf |
| `screenshot.sh` | Region/full screenshot via grim + slurp |
| `screenrec.sh` | Screen recording |
| `screenrotationacw.sh` | Rotate screen anti-clockwise (tablet) |
| `screenrotationwcw.sh` | Rotate screen clockwise (tablet) |
| `unified-rotate.sh` | Unified rotation handler |
| `hyprland-toggle-touchpad.sh` | Enable/disable touchpad |
| `wofi-file-search.sh` | File search via wofi |
| `foldersearch.sh` | Folder search via wofi |
| `pwr-dmenu.sh` | Power menu (shutdown/reboot/suspend) |
| `caffeine.sh` | Toggle idle inhibitor |
| `getispeed.sh` | Internet speed test display |
| `journal.sh` | Quick journal entry |
| `date.sh` / `time.sh` | Date/time waybar helpers |
| `uptime.sh` | Uptime display |
| `dysk-phydisks.sh` | Physical disk info (dysk) |
| `drawer.sh` | Open nwg-drawer |
| `menu.sh` | Application menu |
| `onscreenkb.sh` | On-screen keyboard (wvkbd) |
| `bluetooth-applet.sh` | Bluetooth UI wrapper |
| `togglewinbars.sh` | Toggle window title bars |
| `toggle-layout.sh` | Switch tiling layout |
| `playpause.sh` | Media play/pause |
| `calender-fix.sh` | Calendar waybar widget fix |
---
## Application Theming
### Spotify (Spicetify)
Two CyberQueer Spicetify themes are available:
| Theme | Style |
|-------|-------|
| `cli-cyberqueer` | CLI-inspired, minimal |
| `matte-cyberqueer` | Matte finish variant |
Applied automatically when the `spotify` module is installed.
### Discord (Vencord)
Two Discord themes:
| Theme | Location |
|-------|----------|
| `cyberqueer.theme.css` | Standalone CyberQueer theme |
| `system24/…/cyberqueer.theme.css` | system24 framework with CyberQueer colours |
### Terminal (Kitty)
The `kitty/themes/cyberqueer.conf` file defines the full 16-colour palette mapped to CyberQueer values. It is sourced by `current-theme.conf` which is imported in `kitty.conf`.
---
## Login Manager (ly)
`ly` is a TUI display manager configured via `etc-ly-config.ini` (deployed to `/etc/ly/config.ini`). Its colours are tracked by `apply-theme.sh` (system file, applied via `sudo`).
---
## Installing Hyprland
```bash
# Via the TUI installer
bash ~/Dotfiles/setup/tui-install.sh
# → Select "shell" and "Hyprland" in the dialogs
# On an existing system
bash ~/Dotfiles/setup/install-modules.sh
# → Not available; Hyprland is a base DE, use the full installer
# Direct script
bash ~/Dotfiles/setup/modules/Desktop-Environments/hyprland.sh
```
The install script compiles EWW from source (requires Rust), copies all configs, installs the GTK and cursor themes, enables `ly@tty1`, and configures `greetd`.

77
docs/md/index.md Normal file
View File

@ -0,0 +1,77 @@
# M-Archy Dotfiles
**Arch Linux · Hyprland · Wayland · CyberQueer**
A production-grade Arch Linux configuration for network administration, development, and gaming — built around the Hyprland Wayland compositor and the CyberQueer colour theme.
---
## What's Inside
| Area | Description |
|------|-------------|
| [Installation](installation.md) | Interactive TUI installer, answerfile automation, ISO building |
| [Theming](theming.md) | CyberQueer colour system and `apply-theme.sh` |
| [Hyprland](hyprland.md) | Desktop environment, keybindings, bars, launchers |
| [Modules](modules.md) | Core modules and full optional-app catalogue |
| [Archiso](archiso.md) | Building the custom live installer ISO |
| [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 |
---
## Quick Start
```bash
# 1 — clone
git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git ~/Dotfiles
# 2 — run the interactive installer
bash ~/Dotfiles/setup/tui-install.sh
# 3 — optionally apply a custom colour palette
nano ~/Dotfiles/colors.conf
bash ~/Dotfiles/apply-theme.sh
```
For a fully automated install from a live USB, see [Archiso](archiso.md).
---
## CyberQueer Colour Palette
| Role | Name | Hex |
|------|------|-----|
| Background | Dark grey | `#1A1A1A` |
| Text | Rose white | `#D6ABAB` |
| Primary accent | Hot pink | `#E40046` |
| Secondary accent | Electric violet | `#5018DD` |
| Danger / alerts | Red | `#F50505` |
---
## Repository Layout
```
Dotfiles/
├── apply-theme.sh # Propagate colours across all configs
├── colors.conf # Single source of truth for the palette
├── 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
│ └── modules/ # Modular install scripts
├── desktopenvs/hyprland/ # All Hyprland / Wayland configs
├── gtk-themes/cyberqueer/ # GTK 3 & 4 theme
├── qt-themes/cyberqueer/ # Qt platform theme
├── nvim/ # Neovim config
├── micro/ # Micro editor config
├── yazi/ # Yazi file manager config
├── clamav/ # ClamAV on-access scan setup
└── docs/ # This documentation
```

187
docs/md/installation.md Normal file
View File

@ -0,0 +1,187 @@
# Installation
Three paths are available depending on how much you want to automate:
| Path | When to use |
|------|-------------|
| [Interactive TUI](#interactive-tui) | Fresh Arch system, guided dialogs |
| [Answerfile (automated)](#answerfile-automated) | Unattended or repeatable installs |
| [Custom ISO](#custom-live-iso) | Deploy from USB to multiple machines |
---
## Prerequisites
- Arch Linux (base install completed, user created)
- Internet connection
- `git` available (`sudo pacman -S git`)
---
## Interactive TUI
Clone the repo and run the installer:
```bash
git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git ~/Dotfiles
bash ~/Dotfiles/setup/tui-install.sh
```
The TUI walks you through:
1. **Hostname** — optional; sets `/etc/hostname` immediately
2. **Components** — pick any combination:
- `pkg` — package managers (yay, nvm, Rust)
- `core` — 100+ base system packages
- `svc` — core services (NetworkManager, cronie, fail2ban, greetd)
- `shell` — zsh, Neovim, Yazi, Micro, Starship
3. **Desktop Environment** — Hyprland, Sway, KDE Plasma, GNOME, COSMIC, XFCE, LXQt, or none
4. **Applications** — checklist of ~50 optional apps (see [Modules](modules.md))
5. **Colorway** — optional; enter hex values to customise the CyberQueer palette
All activity is logged to `~/dotfiles-install.log`.
### Adding Modules Later
To install additional optional apps on an already-configured system:
```bash
bash ~/Dotfiles/setup/install-modules.sh
```
This presents the same app checklist without re-running core setup.
---
## Answerfile (Automated)
An **answerfile** lets the entire install — base OS _and_ dotfiles — run without any user input.
### Generating an Answerfile
```bash
bash ~/Dotfiles/setup/generate-answerfile.sh [OUTPUT_PATH]
# Default output: ~/answerfile.json
```
This dry-runs every installer dialog and saves your choices. **No software is installed.** Passwords are intentionally excluded — you will be prompted at install time.
### Answerfile Schema
```json
{
"_generated": "2026-05-18T12:00:00+00:00",
"drive": "/dev/sda",
"kernel": "linux",
"hostname": "myhost",
"username": "amir",
"encrypt": true,
"fido2_root": false,
"fido2_user": false,
"run_tui": true,
"components": ["pkg", "core", "svc", "shell"],
"desktop_environment": "hyprland",
"apps": ["firefox-browser", "vscodium", "docker"],
"colors": {
"COLOR_TEXT": "D6ABAB",
"COLOR_BG": "1A1A1A",
"COLOR_HIGHLIGHT": "E40046",
"COLOR_DARK": "5018DD",
"COLOR_RED": "F50505"
}
}
```
| Field | Type | Description |
|-------|------|-------------|
| `drive` | string | Install target (`/dev/sda`, `/dev/nvme0n1`, …) |
| `kernel` | string | `linux`, `linux-lts`, or `linux-zen` |
| `hostname` | string | Base hostname — a MAC-address suffix is appended automatically |
| `username` | string | Primary user account name |
| `encrypt` | bool | Enable LUKS2 root encryption |
| `fido2_root` | bool | Enroll FIDO2 key for LUKS unlock |
| `fido2_user` | bool | Enroll FIDO2 key for PAM login |
| `run_tui` | bool | Run dotfiles setup automatically after base install |
| `components` | array | Dotfiles components to install |
| `desktop_environment` | string | DE name or `"none"` |
| `apps` | array | Optional app IDs (see [Modules](modules.md)) |
| `colors` | object | Optional colour overrides (omit to keep defaults) |
### Hostname Uniqueness
When `hostname` is set in the answerfile, the MAC address of the primary network interface is automatically appended:
```
myhost → myhost-aabbccddee11
```
This prevents hostname conflicts when the same answerfile is used across multiple machines.
### Running with an Answerfile
Place the file at `/answerfile.json` (or set the `ANSWERFILE` environment variable):
```bash
# Use default location
sudo cp ~/answerfile.json /answerfile.json
bash ~/Dotfiles/setup/tui-install.sh
# Or override the path
ANSWERFILE=~/my-setup.json bash ~/Dotfiles/setup/tui-install.sh
```
---
## Base OS Installers
Two scripts install Arch Linux itself (before the dotfiles step):
### Guided Installer (`archbaseos-guided-install.sh`)
Interactive, dialog-based. Prompts for each setting with sensible defaults. Good for hands-on installs where you want to review each option.
```bash
bash ~/installer/archbaseos-guided-install.sh
```
### Auto Installer (`arch-autoinstall.sh`)
Reads all settings from `/answerfile.json` if present; falls back to prompts for anything missing.
```bash
bash ~/installer/arch-autoinstall.sh
```
Both installers perform the same steps:
1. Partition disk (EFI 15 GiB · Root · Swap = RAM size)
2. Optionally encrypt root with LUKS2
3. Format root as Btrfs with `@` and `@home` subvolumes
4. `pacstrap` base system
5. Configure locale, timezone, hostname, user, sudo
6. Set up mkinitcpio hooks and GRUB
7. Optionally run `tui-install.sh` inside the chroot
### Disk Encryption
When encryption is enabled:
- **Primary key**: entered interactively at install time
- **Backup key**: generated automatically from `/dev/urandom`, enrolled into a second LUKS slot, and written to `/_LUKS_BACKUP_KEY` inside the new system (mode `0400`, root-readable only, inside the encrypted container)
- **FIDO2** (optional): enroll a hardware key for passwordless unlock
The backup key can be collected by Ansible — see [FreeIPA & Ansible](freeipa-ansible.md).
### mkinitcpio Hook Sets
| Scenario | Hooks |
|----------|-------|
| No encryption | `base udev autodetect microcode modconf kms consolefont block btrfs filesystems keyboard fsck` |
| LUKS + password | `base udev autodetect microcode modconf kms consolefont block encrypt lvm2 btrfs filesystems keyboard keymap fsck` |
| LUKS + FIDO2 | `base udev systemd autodetect microcode modconf kms consolefont block sd-encrypt lvm2 btrfs filesystems keyboard keymap fsck` |
---
## Custom Live ISO
See [Archiso](archiso.md) for building a bootable USB that embeds the installer and, optionally, a pre-baked answerfile for zero-touch deployment.

191
docs/md/modules.md Normal file
View File

@ -0,0 +1,191 @@
# Modules Reference
The setup system is modular — core components are installed first, then any combination of optional apps can be added. All module scripts are idempotent (safe to re-run).
---
## Core Modules
These are selected during the initial `tui-install.sh` run.
### `pkg` — Package Managers
Installs the AUR helper, language runtimes, and build toolchains:
- **yay** — AUR helper (built from source via `makepkg`)
- **Rust / Cargo** — via `rustup` with the stable toolchain
- **nvm** — Node Version Manager; installs Node.js v22 LTS by default
### `core` — Core Packages
~100 packages including:
`7zip` · `base-devel` · `bluez` · `bluez-utils` · `btop` · `fastfetch` · `fdupes` · `ffmpeg` · `git` · `greetd-tuigreet` · `htop` · `jq` · `less` · `lynx` · `neovim` · `networkmanager` · `openssh` · `pipewire` · `pipewire-alsa` · `pipewire-pulse` · `ripgrep` · `rsync` · `tmux` · `udiskie` · `yazi` · `zram-generator`
Also installs `pamtester` from the AUR.
### `svc` — Core Services
Enables and starts these systemd units:
| Service | Purpose |
|---------|---------|
| `NetworkManager` | Network connectivity |
| `cronie` | Cron daemon |
| `fail2ban` | Brute-force protection |
| `greetd` | Login session manager |
| `udisks2` | Removable media |
Also deploys `greetd-tuigreet` config from the dotfiles.
### `shell` — Shell Setup
- **zsh** with **Oh My Zsh** and plugins (zsh-syntax-highlighting, zsh-autosuggestions)
- **Starship** shell prompt
- **Neovim** with Vim-Plug (see [Editors](editors.md))
- **Micro** editor
- **Yazi** file manager
- Deploys `.bashrc`, `.zshrc`, `starship.toml`, Micro config, Neovim config
---
## Desktop Environments
| ID | Name | Notes |
|----|------|-------|
| `hyprland` | Hyprland | Primary DE — see [Hyprland](hyprland.md) |
| `sway` | Sway | Wayland tiling WM, lighter |
| `kde-plasma` | KDE Plasma | Full-featured with sddm |
| `gnome` | GNOME | Modern Wayland DE with gdm |
| `cosmic` | COSMIC | Rust-based DE from System76 |
| `xfce` | XFCE | Lightweight X11 with lightdm |
| `lxqt` | LXQt | Lightweight Qt X11 with sddm |
---
## Optional Applications
Install via `tui-install.sh` at first install, or add later:
```bash
bash ~/Dotfiles/setup/install-modules.sh
```
### AI & Machine Learning
| ID | Package | Description |
|----|---------|-------------|
| `ollama` | ollama | Local LLM runner with REST API server |
| `llama-cpp` | llama.cpp | Standalone inference CLI + server |
| `open-webui` | open-webui | Browser UI for Ollama / OpenAI-compatible backends |
| `claude` | claude (npm) | Anthropic Claude Code CLI |
### Networking & Security
| ID | Packages | Description |
|----|---------|-------------|
| `networking-cli` | nmap · nethogs · mitmproxy · httpie | Network analysis and HTTP tooling |
| `disk-recovery` | ddrescue · f3 | Disk imaging and flash drive testing |
| `ssh-server` | openssh | SSH daemon with key-auth enforcement |
| `wireshark` | wireshark-qt | Packet capture and analysis GUI |
### Development
| ID | Packages | Description |
|----|---------|-------------|
| `python` | pyright · pipx · pynvim | Python LSP, isolated tool runner, Neovim integration |
| `docker` | docker · docker-compose | Container runtime |
| `podman` | podman · buildah · podman-compose | Rootless containers |
| `cockpit` | cockpit · machines · podman | Web-based system management UI |
| `k8s` | kubectl · podman-desktop | Kubernetes CLI and desktop client |
| `db-clients` | pgcli · mycli | Enhanced interactive database CLIs |
| `mysql` | mariadb | MariaDB server with initial setup |
### IDEs & Editors
| ID | Package | Description |
|----|---------|-------------|
| `vscodium` | vscodium-bin (AUR) | VS Code without telemetry |
| `zed-ide` | zed | High-performance Rust IDE |
| `geany` | geany · geany-plugins | Lightweight IDE |
| `codeblocks` | codeblocks | C/C++ IDE |
| `kate` | kate | KDE advanced text editor |
### Browsers
| ID | Package | Description |
|----|---------|-------------|
| `chromium` | chromium | Open-source Chromium |
| `firefox-browser` | firefox | Mozilla Firefox |
| `zen-browser` | zen-browser-bin (AUR) | Privacy-focused Firefox fork |
| `nyxt` | nyxt (AUR) | Keyboard-driven, hackable browser |
| `librewolf` | librewolf-bin (AUR) | Hardened Firefox fork |
| `min-browser` | min (AUR) | Minimal Electron browser |
### Gaming
| ID | Package | Description |
|----|---------|-------------|
| `steam` | steam | Steam gaming platform |
| `vesktop` | vesktop (AUR) | Discord client with Vencord built-in |
| `spotify` | spotify (AUR) + spicetify | Music player with CyberQueer theme |
| `prism` | prismlauncher (Flatpak) | Minecraft launcher |
| `vintagestory` | vintagestory (AUR) | Survival / voxel game |
### Media & Creative
| ID | Packages | Description |
|----|---------|-------------|
| `ffmpeg` | gst-plugin-pipewire · gst-plugins-good · ffmpegthumbnailer | GStreamer codecs + thumbnailer |
| `sox` | sox | Command-line audio processing |
| `imagemagick` | imagemagick | Image manipulation suite |
| `yt-dlp` | yt-dlp | YouTube / media downloader |
| `blender` | blender | 3D creation suite |
| `gnuplot` | gnuplot | Scientific plotting |
| `povray` | povray | Ray-tracing renderer |
### Productivity
| ID | Packages | Description |
|----|---------|-------------|
| `productivity` | taskwarrior · watson · jrnl | Task management, time tracking, journaling |
| `himalaya` | himalaya (AUR) | Terminal email client |
| `toot` | toot (AUR) | Mastodon CLI client |
### System Utilities
| ID | Packages | Description |
|----|---------|-------------|
| `tlp` | tlp · tlp-rdw | Laptop battery optimisation |
| `zfs` | zfs-dkms | ZFS kernel module |
| `wprs` | wprs-git (AUR) | Wayland proxy for remote sessions |
| `butter` | butter (AUR) | Btrfs snapshot backup manager |
| `localsend` | localsend (AUR) | LAN file transfer (AirDrop-like) |
| `croc` | croc | Cross-platform encrypted file transfer |
| `localtunnel` | localtunnel (npm) | Expose localhost over a public URL |
| `onlyoffice` | onlyoffice-bin (AUR) | Office suite (Docs, Sheets, Slides) |
### Identity & Infrastructure
| ID | Description |
|----|-------------|
| `freeipa-client` | sssd + ipa-client-install + auto-enrollment (see [FreeIPA](freeipa-ansible.md)) |
| `freeipa-server` | Interactive FreeIPA server setup + client generator |
| `freeipa-image` | OCI / LXC / Proxmox LXC image builder + Keycloak |
---
## Container Shell Setups
Scripts in `setup/Setup-shell-4-containers/` configure a minimal shell environment inside containers or chroots for each major distribution:
| Script | Target |
|--------|--------|
| `alpine.sh` | Alpine Linux |
| `arch.sh` | Arch Linux |
| `debian.sh` | Debian |
| `fedora.sh` | Fedora |
| `suse.sh` | openSUSE |
| `ubuntu.sh` | Ubuntu |
| `void.sh` | Void Linux |
| `other.sh` | Generic fallback |

140
docs/md/theming.md Normal file
View File

@ -0,0 +1,140 @@
# CyberQueer Theme System
The CyberQueer theme is a single-source colour system: every config file that needs colours references a small set of hex values that can be changed in one place and propagated everywhere with a single command.
---
## The Palette
Defined in `~/Dotfiles/colors.conf` (bare 6-digit hex, no `#` prefix):
```ini
COLOR_TEXT=D6ABAB # Rose-white — foreground text, labels
COLOR_BG=1A1A1A # Near-black — base surface, backgrounds
COLOR_HIGHLIGHT=E40046 # Hot pink — primary accent, active borders
COLOR_DARK=5018DD # Violet — secondary accent, inactive borders
COLOR_RED=F50505 # Red — danger indicators, alerts
```
---
## Applying the Theme
```bash
# Apply using the default colors.conf
bash ~/Dotfiles/apply-theme.sh
# Apply from a custom palette file
bash ~/Dotfiles/apply-theme.sh /path/to/custom-colors.conf
```
`apply-theme.sh` will:
1. Read `colors.conf` (or the file you pass)
2. Compare against the last-applied state in `~/.config/colors.state`
3. Replace only **changed** colour values across all tracked files
4. Save the new state to `colors.state`
If nothing changed it exits immediately — safe to call repeatedly.
### First-Run Bootstrap
On a fresh install where configs have been copied but no state file exists yet, `apply-theme.sh` bootstraps `~/.config/colors.state` with the repository defaults so the diff works correctly from the start.
---
## What Gets Themed
### User Configs (`~/.config/…`)
| File | What it styles |
|------|---------------|
| `starship.toml` | Shell prompt segment colours |
| `yazi/theme.toml` | File manager UI colours |
| `hypr/hyprland.conf` | Active/inactive window border gradients |
| `hypr/hyprtoolkit.conf` | Additional Hyprland colours |
| `hypr/hyprlock.conf` | Lock screen colours |
| `kitty/current-theme.conf` | Terminal colour palette |
| `kitty/kitty.conf` | Terminal background & accents |
| `kitty/themes/cyberqueer.conf` | Kitty colour scheme definition |
| `waybar/style.css` | Top bar widget colours |
| `wofi/style.css` | App launcher colours |
| `walker/themes/cyberqueer.css` | Walker launcher theme |
| `nwg-dock-hyprland/style.css` | Application dock |
| `nwg-drawer/drawer.css` | Application drawer |
| `nwg-panel/menu-start.css` | Panel start menu |
| `vicinae/cyberqueer.toml` | Gesture launcher |
| `scripts/onscreenkb.sh` | On-screen keyboard colours |
| `spicetify/Themes/*/color.ini` | Spotify client theme (×2 variants) |
| `ulauncher/user-themes/cyberqueer/manifest.json` | uLauncher theme |
| `ulauncher/user-themes/cyberqueer/theme.css` | uLauncher CSS |
| `ulauncher/user-themes/cyberqueer/generated.css` | uLauncher generated CSS |
| `Vencord/themes/cyberqueer.theme.css` | Discord theme |
| `Vencord/themes/system24/…/cyberqueer.theme.css` | Discord system24 variant |
### System Files (applied via `sudo`)
| File | What it styles |
|------|---------------|
| `/etc/ly/config.ini` | TUI login manager colours |
| `/usr/share/themes/cyberqueer/gtk-3.0/gtk.css` | GTK 3 theme |
| `/usr/share/themes/cyberqueer/gtk-4.0/gtk.css` | GTK 4 theme |
---
## Customising the Palette
Edit `~/Dotfiles/colors.conf`, then run `apply-theme.sh`:
```bash
# Example: shift the accent to cyan
nano ~/Dotfiles/colors.conf
# → COLOR_HIGHLIGHT=00B4D8
bash ~/Dotfiles/apply-theme.sh
```
The tui-install.sh installer also offers a colorway dialog as its final step:
enter new hex values in the form; leave them unchanged to skip.
---
## How It Works Internally
`apply-theme.sh` reads two keyvalue files and computes the diff:
```
~/.config/colors.state (old values — what's currently applied)
colors.conf (new values — what you want)
```
For each changed key it runs:
```bash
sed -i "s/${OLD_HEX}/${NEW_HEX}/gI" <file>
```
The case-insensitive (`I`) flag matches uppercase hex codes that some apps emit. After all replacements succeed, `colors.state` is updated.
### Symlink Guard
`apply-theme.sh` refuses to run if any deployed config path resolves back into `~/Dotfiles/` via symlink. This prevents theme changes from being committed directly into the git repository. The new-style install (via `tui-install.sh`) **copies** configs instead of symlinking them, so this guard is normally never triggered.
---
## Answerfile Theming
If you generate an answerfile with `generate-answerfile.sh`, custom colours can be embedded in it:
```json
{
"colors": {
"COLOR_TEXT": "D6ABAB",
"COLOR_BG": "1A1A1A",
"COLOR_HIGHLIGHT": "E40046",
"COLOR_DARK": "5018DD",
"COLOR_RED": "F50505"
}
}
```
`tui-install.sh` will apply these at the end of an automated install.

173
docs/md/utilities.md Normal file
View File

@ -0,0 +1,173 @@
# Utilities
Miscellaneous scripts and tools that live at the top level or in the `clamav/` directory.
---
## System Update
```bash
bash ~/Dotfiles/update.sh
```
Runs a full system update:
1. `sudo pacman -Syu` — official repos
2. `yay -Syu --answerdiff None --answerclean All --removemake` — AUR packages (no prompts)
For per-package AUR updates with confirmation:
```bash
bash ~/Dotfiles/update-aur-onebyone.sh
```
---
## Package Audit
```bash
bash ~/Dotfiles/setup/audit-packages.sh
```
Audits installed packages — useful for finding orphans or unexpected installations.
---
## Encryption Utilities
Simple OpenSSL wrappers for encrypting/decrypting arbitrary strings. Useful for storing secrets in scripts or config files without plaintext exposure.
### Encrypt
```bash
bash ~/Dotfiles/encrypt.sh "my secret text" "my-passphrase"
# Output: base64-encoded AES-256-CBC ciphertext
```
### Decrypt
```bash
bash ~/Dotfiles/decrypt.sh "<ciphertext>" "my-passphrase"
# Output: original plaintext
```
Both use AES-256-CBC with PBKDF2 key derivation via OpenSSL.
---
## Credential Storage
### Initial Setup
```bash
bash ~/Dotfiles/setup-creds-missing.sh
```
Installs `gnome-keyring` and `seahorse` (GUI manager), then sets git's credential helper to `store`.
### Git Credentials
`git/` contains `.gitconfig` with:
```ini
[user]
name = The_miro
email = amir@abdelbaki.eu
[credential]
helper = store
[init]
defaultBranch = main
[push]
autoSetupRemote = true
```
The `store` helper writes credentials to `~/.git-credentials`. For higher security, `gnome-keyring` intercepts this and stores the credentials in the system keyring instead of plaintext.
---
## Zsh Plugins
```bash
bash ~/Dotfiles/zshplugins.sh
```
Clones (or updates) the two Oh My Zsh community plugins:
- `zsh-syntax-highlighting` — real-time syntax colouring in the prompt
- `zsh-autosuggestions` — fish-style history-based suggestions
These are referenced in `.zshrc` and active after the next shell start.
---
## ClamAV On-Access Scanning
Full real-time antivirus scanning via ClamAV's `clamonacc` daemon.
### Installation
```bash
bash ~/Dotfiles/clamav/install-clam-onaccess.sh
```
What it does:
1. Installs `clamav`
2. Copies `clamd.conf` to `/etc/clamav/`
3. Installs `clamav-clamonacc.service` to `/etc/systemd/system/`
4. Installs the sudoers entry from `clamav-sudoer`
5. Updates virus definitions (`freshclam`)
6. Enables and starts `clamd` + `clamav-clamonacc`
### Key Files
| File | Purpose |
|------|---------|
| `clamav/clamd.conf` | Daemon configuration (30 KB, full options) |
| `clamav/clamav-clamonacc.service` | systemd unit for on-access scanning |
| `clamav/clamav-sudoer` | sudoers rule for ClamAV processes |
| `clamav/virus-event.bash` | Handler executed when a virus is detected |
### Virus Event Handler
`virus-event.bash` is called by clamonacc when a threat is found. Customise it to send notifications, quarantine files, or alert an admin.
---
## Shell Configuration
### `.zshrc`
- **Framework**: Oh My Zsh
- **Theme**: robbyrussell (overridden visually by Starship)
- **Plugins**: syntax-highlighting, autosuggestions
- **Walk integration**: `lk` function opens the `walk` file navigator
- **`WALK_MAIN_COLOR`**: set to `#5018DD` (CyberQueer violet)
- Sources Starship init at the end
### `.bashrc`
Minimal bash config — sets `PS1`, loads `~/.bash_profile` if present.
### Starship Prompt
`starship.toml` at the repo root is deployed to `~/.config/starship.toml`.
Key customisations:
- OS, username, directory, git, language, docker, and time segments
- CyberQueer colours throughout (colour-substitution target)
- Directory abbreviated to 3 levels with `…/` truncation
- Common directory substitutions (`~/Documents` → `📄`, etc.)
---
## Login Manager (ly)
`etc-ly-config.ini` is deployed to `/etc/ly/config.ini` during Hyprland install and kept as a colour-substitution target in `apply-theme.sh`.
ly is a minimal TUI display manager that runs on `tty1`:
```
systemctl enable ly@tty1
```
Session selection, auto-login, and timeout settings are all in the config.

View File

@ -1,11 +1,18 @@
#!/usr/bin/env bash
# arch-autoinstall.sh — automated Arch Linux base installer
#
# If /answerfile.json exists (e.g. embedded in the ISO via build.sh --preconf),
# all prompts are answered from it. Missing fields fall back to interactive prompts.
#
# Answerfile fields: drive, kernel, hostname, username, encrypt, fido2_root,
# fido2_user, run_tui (password always prompted interactively)
set -euo pipefail
############################################
# LOGGING
############################################
LOGFILE="$HOME/arch-autoinstall.log"
{
echo
echo "############################################"
@ -13,16 +20,53 @@ LOGFILE="$HOME/arch-autoinstall.log"
echo "############################################"
echo
} >> "$LOGFILE"
exec > >(tee -a "$LOGFILE") 2>&1
############################################
# ANSWERFILE
############################################
ANSWERFILE="${ANSWERFILE:-/answerfile.json}"
AF_MODE=false
[[ -f "$ANSWERFILE" ]] && AF_MODE=true
af_get() {
# af_get <jq-filter> [default]
local val
val=$(jq -r "${1} // empty" "$ANSWERFILE" 2>/dev/null || true)
if [[ -z "$val" ]]; then printf '%s' "${2:-}"; else printf '%s' "$val"; fi
}
af_bool() {
# Returns YES or NO from a JSON boolean field
local val; val=$(jq -r "${1} // false" "$ANSWERFILE" 2>/dev/null || true)
[[ "$val" == "true" ]] && echo "YES" || echo "NO"
}
get_mac_suffix() {
local mac
mac=$(ip link show 2>/dev/null \
| awk '/^[0-9]+: [^l][^o]/{iface=1} iface && /link\/ether/{print $2; iface=0; exit}')
printf '%s' "${mac//:/}"
}
if $AF_MODE; then
echo "Answerfile detected: $ANSWERFILE"
# Ensure jq is available
command -v jq &>/dev/null || pacman -Sy --noconfirm jq
fi
############################################
# SAFETY WARNING
############################################
echo "WARNING: This will ERASE ALL DATA on the selected drive!"
read -rp "Type 'YES' to continue: " confirm
[[ "$confirm" == "YES" ]] || { echo "Aborted."; exit 1; }
if $AF_MODE; then
echo "WARNING: Automated install — all data on $(af_get '.drive' '/dev/?') will be ERASED."
echo "Proceeding in 5 seconds... (Ctrl-C to abort)"
sleep 5
else
echo "WARNING: This will ERASE ALL DATA on the selected drive!"
read -rp "Type 'YES' to continue: " confirm
[[ "$confirm" == "YES" ]] || { echo "Aborted."; exit 1; }
fi
############################################
# REQUIRED PACKAGES FOR INSTALL ENVIRONMENT
@ -30,10 +74,57 @@ read -rp "Type 'YES' to continue: " confirm
pacman -Sy --noconfirm parted cryptsetup libfido2 pam-u2f
############################################
# DISK SELECTION
# DRIVE SELECTION
############################################
lsblk
read -rp "Enter target drive (e.g., /dev/sda): " DRIVE
if $AF_MODE && [[ -n "$(af_get '.drive')" ]]; then
DRIVE=$(af_get '.drive')
echo "Drive (from answerfile): $DRIVE"
else
read -rp "Enter target drive (e.g., /dev/sda): " DRIVE
fi
############################################
# USER INPUT
############################################
if $AF_MODE; then
KERNEL=$(af_get '.kernel' 'linux')
RAW_HOSTNAME=$(af_get '.hostname' '')
if [[ -n "$RAW_HOSTNAME" ]]; then
HOSTNAME="${RAW_HOSTNAME}-$(get_mac_suffix)"
else
HOSTNAME="arch"
fi
USERNAME=$(af_get '.username' '')
ENCRYPT_DISK=$(af_bool '.encrypt')
FIDO_ROOT=$(af_bool '.fido2_root')
FIDO_USER=$(af_bool '.fido2_user')
RUN_TUI=$(af_bool '.run_tui')
echo "Kernel: $KERNEL"
echo "Hostname: $HOSTNAME"
echo "Username: $USERNAME"
echo "Encrypt: $ENCRYPT_DISK / FIDO2 root: $FIDO_ROOT / FIDO2 user: $FIDO_USER"
else
read -rp "Enter kernel package (e.g., linux, linux-lts): " KERNEL
read -rp "Enter hostname: " HOSTNAME
read -rp "Enter username: " USERNAME
read -rp "Enable disk encryption? (YES/NO): " ENCRYPT_DISK
FIDO_ROOT="NO"
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
read -rp "Enable FIDO2 unlocking for root partition? (YES/NO): " FIDO_ROOT
fi
read -rp "Enable FIDO2 authentication for user login? (YES/NO): " FIDO_USER
fi
# Password always prompted — never stored in answerfile
read -rsp "Enter password for $USERNAME: " USERPASS; echo
[[ -z "$USERPASS" ]] && { echo "Error: password cannot be empty."; exit 1; }
if ! $AF_MODE; then
read -rp "Run dotfiles TUI setup inside chroot now? [YES/no]: " _RUN_TUI_IN
_RUN_TUI_IN="${_RUN_TUI_IN:-YES}"
[[ "${_RUN_TUI_IN^^}" == "YES" ]] && RUN_TUI="YES" || RUN_TUI="NO"
fi
############################################
# RAM / PARTITION SIZING
@ -44,7 +135,6 @@ SWAP_SIZE="${RAM_GB}GiB"
DISK_SIZE=$(lsblk -b -dn -o SIZE "$DRIVE")
DISK_GIB=$((DISK_SIZE / 1024 / 1024 / 1024))
ROOT_GIB=$((DISK_GIB - RAM_GB - 15))
echo "Partition plan:"
@ -73,44 +163,74 @@ mkswap "$SWAP_PART"
swapon "$SWAP_PART"
############################################
# ASK ABOUT FIDO2 LUKS ENROLLMENT
# ENCRYPTION (OPTIONAL)
############################################
read -rp "Enable FIDO2 unlocking for root partition? (YES/NO): " FIDO_ROOT
LUKS_BACKUP_KEY="" # path to key file, set only when encryption is active
############################################
# LUKS ENCRYPT ROOT
############################################
echo "Encrypting root partition..."
cryptsetup -v luksFormat "$ROOT_PART"
cryptsetup open "$ROOT_PART" cryptroot
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
echo "Encrypting root partition..."
cryptsetup -v luksFormat "$ROOT_PART"
cryptsetup open "$ROOT_PART" cryptroot
############################################
# OPTIONAL FIDO2 ENROLLMENT
############################################
if [[ "$FIDO_ROOT" == "YES" ]]; then
echo "Insert FIDO2 key for LUKS and touch when prompted..."
systemd-cryptenroll "$ROOT_PART" --fido2-device=auto --fido2-with-client-pin=no
# ── Auto-generate backup LUKS key ──────────────────────────────────────────
# A random key is enrolled as a second LUKS slot so recovery is possible
# without the primary passphrase. It is written to /_LUKS_BACKUP_KEY in the
# new system (inside the encrypted container) where only root can read it.
LUKS_BACKUP_KEY=$(mktemp /tmp/luks-backup-key.XXXXXX)
dd if=/dev/urandom bs=64 count=1 2>/dev/null | base64 -w0 > "$LUKS_BACKUP_KEY"
echo "Enrolling auto-generated backup LUKS key..."
cryptsetup luksAddKey "$ROOT_PART" "$LUKS_BACKUP_KEY"
# ── Optional FIDO2 enrollment ─────────────────────────────────────────────
if [[ "$FIDO_ROOT" == "YES" ]]; then
echo "Insert FIDO2 key for LUKS and touch when prompted..."
systemd-cryptenroll "$ROOT_PART" --fido2-device=auto --fido2-with-client-pin=no
fi
############################################
# BTRFS ON ENCRYPTED ROOT
############################################
mkfs.btrfs /dev/mapper/cryptroot
mount /dev/mapper/cryptroot /mnt
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home
umount /mnt
mount -o subvol=@ /dev/mapper/cryptroot /mnt
mkdir -p /mnt/home
mount -o subvol=@home /dev/mapper/cryptroot /mnt/home
else
echo "Skipping encryption — formatting root directly."
############################################
# BTRFS ON UNENCRYPTED ROOT
############################################
mkfs.btrfs "$ROOT_PART"
mount "$ROOT_PART" /mnt
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home
umount /mnt
mount -o subvol=@ "$ROOT_PART" /mnt
mkdir -p /mnt/home
mount -o subvol=@home "$ROOT_PART" /mnt/home
fi
############################################
# BTRFS SUBVOLUMES
############################################
mkfs.btrfs /dev/mapper/cryptroot
mount /dev/mapper/cryptroot /mnt
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home
umount /mnt
mount -o subvol=@ /dev/mapper/cryptroot /mnt
mkdir /mnt/home
mount -o subvol=@home /dev/mapper/cryptroot /mnt/home
mkdir /mnt/boot
mkdir -p /mnt/boot
mount "$BOOT_PART" /mnt/boot
# Place backup key inside the new system (only accessible when disk is unlocked)
if [[ -n "$LUKS_BACKUP_KEY" ]]; then
install -m 400 "$LUKS_BACKUP_KEY" /mnt/_LUKS_BACKUP_KEY
rm -f "$LUKS_BACKUP_KEY"
echo "Backup LUKS key written to /_LUKS_BACKUP_KEY in new system."
fi
############################################
# GPU DETECTION
############################################
GPU_INFO=$(lspci | grep -E "VGA|3D")
GPU_INFO=$(lspci | grep -E "VGA|3D" || true)
GPU_PKGS=""
if echo "$GPU_INFO" | grep -qi "NVIDIA"; then
GPU_PKGS="nvidia nvidia-utils"
@ -119,39 +239,36 @@ elif echo "$GPU_INFO" | grep -qi "AMD"; then
elif echo "$GPU_INFO" | grep -qi "Intel"; then
GPU_PKGS="xf86-video-intel"
fi
echo "Detected GPU: $GPU_INFO"
############################################
# USER INPUT
############################################
read -rp "Enter kernel package (e.g., linux, linux-lts): " KERNEL
read -rp "Enter hostname: " HOSTNAME
read -rp "Enter username: " USERNAME
read -rsp "Enter password for $USERNAME: " USERPASS
echo
read -rp "Enable FIDO2 authentication for user login? (YES/NO): " FIDO_USER
echo "Detected GPU: ${GPU_INFO:-none}"
############################################
# BASE INSTALL
############################################
# 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 $GPU_PKGS
networkmanager grub cryptsetup libfido2 pam-u2f efibootmgr sudo btrfs-progs lvm2 jq $GPU_PKGS
############################################
# FSTAB
############################################
genfstab -U /mnt >> /mnt/etc/fstab
############################################
# COPY ANSWERFILE INTO NEW SYSTEM
############################################
if $AF_MODE; then
install -m 644 "$ANSWERFILE" /mnt/answerfile.json
fi
############################################
# PASS VARIABLES INTO CHROOT
############################################
export HOSTNAME USERNAME USERPASS ROOT_PART FIDO_ROOT FIDO_USER
export HOSTNAME USERNAME USERPASS ROOT_PART KERNEL FIDO_ROOT FIDO_USER ENCRYPT_DISK
############################################
# CHROOT CONFIGURATION
############################################
arch-chroot /mnt /bin/bash <<'EOF'
arch-chroot /mnt /bin/bash <<'CHROOT_EOF'
set -euo pipefail
# Locale
@ -175,10 +292,12 @@ echo "%wheel ALL=(ALL:ALL) ALL" >> /etc/sudoers
###################################################
# INITRAMFS CONFIG
###################################################
if [[ "$FIDO_ROOT" == "YES" ]]; then
sed -i 's/^HOOKS=.*/HOOKS=(base btrfs udev systemd microcode kms autodetect modconf consolefont block sd-encrypt encrypt lvm2 filesystems keyboard keymap fsck)/' /etc/mkinitcpio.conf
if [[ "$ENCRYPT_DISK" == "YES" && "$FIDO_ROOT" == "YES" ]]; then
sed -i 's/^HOOKS=.*/HOOKS=(base udev systemd autodetect microcode modconf kms consolefont block sd-encrypt lvm2 btrfs filesystems keyboard keymap fsck)/' /etc/mkinitcpio.conf
elif [[ "$ENCRYPT_DISK" == "YES" ]]; then
sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect microcode modconf kms consolefont block encrypt lvm2 btrfs filesystems keyboard keymap fsck)/' /etc/mkinitcpio.conf
else
sed -i 's/^HOOKS=.*/HOOKS=(base btrfs udev systemd microcode kms autodetect modconf consolefont block encrypt encrypt lvm2 filesystems keyboard keymap fsck)/' /etc/mkinitcpio.conf
sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect microcode modconf kms consolefont block btrfs filesystems keyboard fsck)/' /etc/mkinitcpio.conf
fi
mkinitcpio -p "$KERNEL"
@ -187,49 +306,46 @@ mkinitcpio -p "$KERNEL"
# GRUB CONFIG
###################################################
UUID=$(blkid -s UUID -o value "$ROOT_PART")
if [[ "$FIDO_ROOT" == "YES" ]]; then
KERNEL_CMD="rd.luks.name=${UUID}=cryptroot root=/dev/mapper/cryptroot"
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
if [[ "$FIDO_ROOT" == "YES" ]]; then
KERNEL_CMD="rd.luks.name=${UUID}=cryptroot root=/dev/mapper/cryptroot"
else
KERNEL_CMD="cryptdevice=UUID=${UUID}:cryptroot root=/dev/mapper/cryptroot"
fi
else
KERNEL_CMD="cryptdevice=UUID=${UUID}:cryptroot root=/dev/mapper/cryptroot"
KERNEL_CMD="root=UUID=${UUID} rootflags=subvol=@"
fi
sed -i "s|^GRUB_CMDLINE_LINUX=.*|GRUB_CMDLINE_LINUX=\"$KERNEL_CMD\"|" /etc/default/grub
grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=M-Archy-GRUB-CuIn
grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=M-Archy-GRUB
grub-mkconfig -o /boot/grub/grub.cfg
###################################################
# USER FIDO2 LOGIN
###################################################
if [[ "$FIDO_USER" == "YES" ]]; then
mkdir -p /home/$USERNAME/.config/Yubico
mkdir -p "/home/$USERNAME/.config/Yubico"
echo "Insert FIDO2 key for user login and touch when prompted..."
sudo -u "$USERNAME" pamu2fcfg -u "$USERNAME" > /home/$USERNAME/.config/Yubico/u2f_keys
chown "$USERNAME":"$USERNAME" /home/$USERNAME/.config/Yubico/u2f_keys
sudo -u "$USERNAME" pamu2fcfg -u "$USERNAME" > "/home/$USERNAME/.config/Yubico/u2f_keys"
chown "$USERNAME":"$USERNAME" "/home/$USERNAME/.config/Yubico/u2f_keys"
echo "auth required pam_u2f.so" >> /etc/pam.d/system-local-login
fi
###################################################
# CLONE DOTFILES FOR POST-INSTALL SETUP
# CLONE DOTFILES
###################################################
echo "Cloning dotfiles..."
git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git /home/$USERNAME/Dotfiles \
&& chown -R $USERNAME:$USERNAME /home/$USERNAME/Dotfiles \
git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git "/home/$USERNAME/Dotfiles" \
&& chown -R "$USERNAME":"$USERNAME" "/home/$USERNAME/Dotfiles" \
|| echo "Warning: dotfiles clone failed — clone manually after first boot."
EOF
CHROOT_EOF
############################################
# DOTFILES SETUP (in-chroot, optional)
# DOTFILES TUI SETUP (in-chroot, optional)
############################################
echo
read -rp "Run dotfiles TUI setup inside chroot now? [YES/no]: " _RUN_TUI
_RUN_TUI="${_RUN_TUI:-YES}"
if [[ "${_RUN_TUI^^}" == "YES" ]]; then
if [[ "${RUN_TUI^^}" == "YES" ]]; then
echo "${USERNAME} ALL=(ALL) NOPASSWD: ALL" \
| arch-chroot /mnt tee /etc/sudoers.d/99-setup-nopasswd > /dev/null
@ -241,43 +357,47 @@ if [[ "${_RUN_TUI^^}" == "YES" ]]; then
arch-chroot /mnt rm -f /etc/sudoers.d/99-setup-nopasswd
fi
# Remove answerfile from new system after setup is complete (contains sensitive paths/config)
if $AF_MODE && [[ -f /mnt/answerfile.json ]]; then
rm -f /mnt/answerfile.json
fi
############################################
# SUMMARY OUTPUT (NEW)
# SUMMARY
############################################
echo
echo "############################################"
echo " INSTALL SUMMARY"
echo "############################################"
echo "Drive: $DRIVE"
echo "Boot partition: $BOOT_PART"
echo "Root partition: $ROOT_PART"
echo "Swap partition: $SWAP_PART"
echo "Drive: $DRIVE"
echo "Boot partition: $BOOT_PART"
echo "Root partition: $ROOT_PART"
echo "Swap partition: $SWAP_PART"
echo
echo "Hostname: $HOSTNAME"
echo "Username: $USERNAME"
echo "Kernel: $KERNEL"
echo "GPU detected: $GPU_INFO"
echo "Hostname: $HOSTNAME"
echo "Username: $USERNAME"
echo "Kernel: $KERNEL"
echo "GPU detected: ${GPU_INFO:-none}"
echo
echo "Disk encryption: $ENCRYPT_DISK"
echo "FIDO2 root unlock: $FIDO_ROOT"
echo "FIDO2 user login: $FIDO_USER"
[[ "$ENCRYPT_DISK" == "YES" ]] && echo "LUKS backup key: /_LUKS_BACKUP_KEY (in new system)"
echo
echo "Boot size: $BOOT_SIZE"
echo "Root size: ${ROOT_GIB}GiB"
echo "Swap size: $SWAP_SIZE"
echo
echo "Log file saved to: $LOGFILE"
echo "Log file: $LOGFILE"
echo "############################################"
echo
cp $LOGFILE /mnt/boot/
cp "$LOGFILE" /mnt/boot/ 2>/dev/null || true
############################################
# DONE
############################################
echo "Installation complete! You can now unmount and reboot."
echo
if [[ "${_RUN_TUI^^}" != "YES" ]]; then
echo "Installation complete! Unmount and reboot:"
echo " umount -R /mnt && reboot"
if [[ "${RUN_TUI^^}" != "YES" ]]; then
echo
echo "After first boot, login as $USERNAME and run:"
echo " ~/Dotfiles/setup/tui-install.sh"
fi

View File

@ -1,8 +1,13 @@
#!/usr/bin/env bash
# archbaseos-guided-install.sh — guided (dialog-based) Arch Linux base installer
#
# If /answerfile.json exists (e.g. embedded via build.sh --preconf), all prompts
# are answered from it. Missing fields fall back to interactive prompts.
set -euo pipefail
############################################
# Helper Functions
# Helper Functions
############################################
confirm() {
@ -23,20 +28,57 @@ pause() {
}
############################################
# Begin
# ANSWERFILE
############################################
ANSWERFILE="${ANSWERFILE:-/answerfile.json}"
AF_MODE=false
[[ -f "$ANSWERFILE" ]] && AF_MODE=true
echo "== Arch Linux FIDO2-Ready Installer =="
af_get() {
local val
val=$(jq -r "${1} // empty" "$ANSWERFILE" 2>/dev/null || true)
if [[ -z "$val" ]]; then printf '%s' "${2:-}"; else printf '%s' "$val"; fi
}
af_bool() {
local val; val=$(jq -r "${1} // false" "$ANSWERFILE" 2>/dev/null || true)
[[ "$val" == "true" ]] && echo "YES" || echo "NO"
}
get_mac_suffix() {
local mac
mac=$(ip link show 2>/dev/null \
| awk '/^[0-9]+: [^l][^o]/{iface=1} iface && /link\/ether/{print $2; iface=0; exit}')
printf '%s' "${mac//:/}"
}
if $AF_MODE; then
echo "== Arch Linux Guided Installer (answerfile mode) =="
command -v jq &>/dev/null || pacman -Sy --noconfirm jq
else
echo "== Arch Linux FIDO2-Ready Installer =="
fi
############################################
# Begin
############################################
lsblk
DRIVE=$(ask "Enter install drive (e.g., /dev/sda)")
confirm "$DRIVE" || exit 1
if $AF_MODE && [[ -n "$(af_get '.drive')" ]]; then
DRIVE=$(af_get '.drive')
echo "Drive (from answerfile): $DRIVE"
echo "WARNING: All data on $DRIVE will be erased. Proceeding in 5 seconds..."
sleep 5
else
DRIVE=$(ask "Enter install drive (e.g., /dev/sda)")
confirm "$DRIVE" || exit 1
fi
# Required packages
pacman -Syd --noconfirm parted cryptsetup libfido2 pam-u2f systemd-ukify
pacman -Syd --noconfirm parted cryptsetup libfido2 pam-u2f systemd-ukify jq
############################################
# Partitioning
# Partitioning
############################################
RAM_GB=$(free --giga | awk '/Mem/ {print $2}')
@ -68,53 +110,101 @@ mkswap "$SWAP_PART"
swapon "$SWAP_PART"
############################################
# Encryption
# User input
############################################
echo
read -rp "Enable FIDO2 for unlocking root? (YES/NO): " ENABLE_FIDO_ROOT
echo "Formatting LUKS2 root..."
cryptsetup luksFormat "$ROOT_PART" --type luks2
cryptsetup open "$ROOT_PART" cryptroot
if [[ $ENABLE_FIDO_ROOT == "YES" ]]; then
echo "Enroll FIDO2 key for LUKS2"
pause
systemd-cryptenroll "$ROOT_PART" --fido2-device=auto --fido2-with-client-pin=no
if $AF_MODE; then
KERNEL=$(af_get '.kernel' 'linux')
RAW_HOSTNAME=$(af_get '.hostname' '')
if [[ -n "$RAW_HOSTNAME" ]]; then
HOSTNAME="${RAW_HOSTNAME}-$(get_mac_suffix)"
else
HOSTNAME="arch"
fi
USERNAME=$(af_get '.username' '')
ENCRYPT_DISK=$(af_bool '.encrypt')
ENABLE_FIDO_ROOT=$(af_bool '.fido2_root')
ENABLE_FIDO_USER=$(af_bool '.fido2_user')
RUN_TUI=$(af_bool '.run_tui')
echo "Kernel: $KERNEL / Hostname: $HOSTNAME / Username: $USERNAME"
echo "Encrypt: $ENCRYPT_DISK / FIDO2 root: $ENABLE_FIDO_ROOT / FIDO2 user: $ENABLE_FIDO_USER"
else
KERNEL=$(ask "Kernel (linux, linux-lts, linux-zen)")
HOSTNAME=$(ask "Hostname")
USERNAME=$(ask "Username")
read -rp "Enable disk encryption? (YES/NO): " ENCRYPT_DISK
ENABLE_FIDO_ROOT="NO"
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
read -rp "Enable FIDO2 for unlocking root? (YES/NO): " ENABLE_FIDO_ROOT
fi
read -rp "Enable FIDO2 for user login? (YES/NO): " ENABLE_FIDO_USER
fi
# Add fallback password
echo "Adding fallback LUKS password (recommended)"
cryptsetup luksAddKey "$ROOT_PART"
read -rsp "Password for $USERNAME: " USERPASS; echo
[[ -z "$USERPASS" ]] && { echo "Error: password cannot be empty."; exit 1; }
############################################
# Filesystem
# Encryption (optional)
############################################
LUKS_BACKUP_KEY=""
mkfs.btrfs /dev/mapper/cryptroot
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
echo "Formatting LUKS2 root..."
cryptsetup luksFormat "$ROOT_PART" --type luks2
cryptsetup open "$ROOT_PART" cryptroot
mount /dev/mapper/cryptroot /mnt
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home
umount /mnt
# ── Auto-generate backup LUKS key ─────────────────────────────────────────
LUKS_BACKUP_KEY=$(mktemp /tmp/luks-backup-key.XXXXXX)
dd if=/dev/urandom bs=64 count=1 2>/dev/null | base64 -w0 > "$LUKS_BACKUP_KEY"
echo "Enrolling auto-generated backup LUKS key..."
cryptsetup luksAddKey "$ROOT_PART" "$LUKS_BACKUP_KEY"
mount -o subvol=@ /dev/mapper/cryptroot /mnt
mkdir -p /mnt/home
mount -o subvol=@home /dev/mapper/cryptroot /mnt/home
if [[ "$ENABLE_FIDO_ROOT" == "YES" ]]; then
echo "Enroll FIDO2 key for LUKS2"
pause
systemd-cryptenroll "$ROOT_PART" --fido2-device=auto --fido2-with-client-pin=no
fi
mkfs.btrfs /dev/mapper/cryptroot
mount /dev/mapper/cryptroot /mnt
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home
umount /mnt
mount -o subvol=@ /dev/mapper/cryptroot /mnt
mkdir -p /mnt/home
mount -o subvol=@home /dev/mapper/cryptroot /mnt/home
else
echo "Skipping encryption — formatting root directly."
mkfs.btrfs "$ROOT_PART"
mount "$ROOT_PART" /mnt
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home
umount /mnt
mount -o subvol=@ "$ROOT_PART" /mnt
mkdir -p /mnt/home
mount -o subvol=@home "$ROOT_PART" /mnt/home
fi
mkdir -p /mnt/boot
mount "$EFI_PART" /mnt/boot
############################################
# Base System Install
############################################
# Place backup key inside the new system (readable only by root, inside LUKS container)
if [[ -n "$LUKS_BACKUP_KEY" ]]; then
install -m 400 "$LUKS_BACKUP_KEY" /mnt/_LUKS_BACKUP_KEY
rm -f "$LUKS_BACKUP_KEY"
echo "Backup LUKS key written to /_LUKS_BACKUP_KEY in new system."
fi
GPU_INFO=$(lspci | grep -E "VGA|3D")
############################################
# Base System Install
############################################
GPU_INFO=$(lspci | grep -E "VGA|3D" || true)
GPU_PKGS=""
if echo "$GPU_INFO" | grep -qi nvidia; then
#GPU_PKGS="nvidia nvidia-utils"
GPU_PKGS="nvidia-open"
elif echo "$GPU_INFO" | grep -qi amd; then
GPU_PKGS="xf86-video-amdgpu"
@ -122,34 +212,35 @@ elif echo "$GPU_INFO" | grep -qi intel; then
GPU_PKGS="xf86-video-intel"
fi
KERNEL=$(ask "Kernel (linux, linux-lts, linux-zen)")
HOSTNAME=$(ask "Hostname")
USERNAME=$(ask "Username")
read -rsp "Password for $USERNAME: " USERPASS; echo
read -rp "Enable FIDO2 for user login? (YES/NO): " ENABLE_FIDO_USER
# shellcheck disable=SC2086
pacstrap /mnt \
base base-devel "$KERNEL" linux-firmware vim zsh git networkmanager grub efibootmgr \
btrfs-progs cryptsetup libfido2 pam-u2f sudo less "$GPU_PKGS"
btrfs-progs cryptsetup libfido2 pam-u2f sudo less jq $GPU_PKGS
genfstab -U /mnt >> /mnt/etc/fstab
############################################
# CHROOT Configuration
# COPY ANSWERFILE INTO NEW SYSTEM
############################################
if $AF_MODE; then
install -m 644 "$ANSWERFILE" /mnt/answerfile.json
fi
############################################
# CHROOT Configuration
############################################
ROOT_UUID=$(blkid -s UUID -o value "$ROOT_PART")
arch-chroot /mnt /usr/bin/env \
HOSTNAME="$HOSTNAME" \
USERNAME="$USERNAME" \
USERPASS="$USERPASS" \
ENCRYPT_DISK="$ENCRYPT_DISK" \
ENABLE_FIDO_ROOT="$ENABLE_FIDO_ROOT" \
ENABLE_FIDO_USER="$ENABLE_FIDO_USER" \
ROOT_UUID="$ROOT_UUID" \
/bin/bash <<'EOF'
ROOT_PART="$ROOT_PART" \
/bin/bash <<'CHROOT_EOF'
set -euo pipefail
@ -176,19 +267,26 @@ chown -R "$USERNAME:$USERNAME" "/home/$USERNAME"
echo "%wheel ALL=(ALL) ALL" >> /etc/sudoers
# Initramfs + GRUB for FIDO2
if [[ "$ENABLE_FIDO_ROOT" == "YES" ]]; then
sed -i 's/^HOOKS=.*/HOOKS=(base systemd autodetect modconf block sd-encrypt filesystems keyboard fsck)/' /etc/mkinitcpio.conf
# Initramfs
if [[ "$ENCRYPT_DISK" == "YES" && "$ENABLE_FIDO_ROOT" == "YES" ]]; then
sed -i 's/^HOOKS=.*/HOOKS=(base udev systemd autodetect microcode modconf kms consolefont block sd-encrypt lvm2 btrfs filesystems keyboard keymap fsck)/' /etc/mkinitcpio.conf
elif [[ "$ENCRYPT_DISK" == "YES" ]]; then
sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect microcode modconf kms consolefont block encrypt lvm2 btrfs filesystems keyboard keymap fsck)/' /etc/mkinitcpio.conf
else
sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect modconf block encrypt filesystems keyboard fsck)/' /etc/mkinitcpio.conf
sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect microcode modconf kms consolefont block btrfs filesystems keyboard fsck)/' /etc/mkinitcpio.conf
fi
mkinitcpio -P
if [[ "$ENABLE_FIDO_ROOT" == "YES" ]]; then
GRUB_CMDLINE="rd.luks.name=$ROOT_UUID=cryptroot rd.luks.options=fido2-device=auto root=/dev/mapper/cryptroot"
# GRUB
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
if [[ "$ENABLE_FIDO_ROOT" == "YES" ]]; then
GRUB_CMDLINE="rd.luks.name=$ROOT_UUID=cryptroot rd.luks.options=fido2-device=auto root=/dev/mapper/cryptroot"
else
GRUB_CMDLINE="cryptdevice=UUID=$ROOT_UUID:cryptroot root=/dev/mapper/cryptroot"
fi
else
GRUB_CMDLINE="cryptdevice=UUID=$ROOT_UUID:cryptroot root=/dev/mapper/cryptroot"
GRUB_CMDLINE="root=UUID=${ROOT_UUID} rootflags=subvol=@"
fi
sed -i "s|^GRUB_CMDLINE_LINUX=.*|GRUB_CMDLINE_LINUX=\"$GRUB_CMDLINE\"|" /etc/default/grub
@ -199,23 +297,26 @@ grub-mkconfig -o /boot/grub/grub.cfg
# User login FIDO2
if [[ "$ENABLE_FIDO_USER" == "YES" ]]; then
echo "Enrolling FIDO2 for user login"
mkdir -p /home/$USERNAME/.config/Yubico
chown $USERNAME:$USERNAME /home/$USERNAME/.config/Yubico
mkdir -p "/home/$USERNAME/.config/Yubico"
chown "$USERNAME:$USERNAME" "/home/$USERNAME/.config/Yubico"
sudo -u "$USERNAME" bash -c "pamu2fcfg >> /home/$USERNAME/.config/Yubico/u2f_keys"
echo "auth required pam_u2f.so" >> /etc/pam.d/system-auth
fi
EOF
CHROOT_EOF
############################################
# DOTFILES SETUP (in-chroot, optional)
# DOTFILES SETUP (in-chroot, optional)
############################################
echo
read -rp "Run dotfiles TUI setup inside chroot now? [YES/no]: " _RUN_TUI
_RUN_TUI="${_RUN_TUI:-YES}"
if $AF_MODE; then
_DO_TUI="${RUN_TUI}"
else
read -rp "Run dotfiles TUI setup inside chroot now? [YES/no]: " _TUI_IN
_TUI_IN="${_TUI_IN:-YES}"
[[ "${_TUI_IN^^}" == "YES" ]] && _DO_TUI="YES" || _DO_TUI="NO"
fi
if [[ "${_RUN_TUI^^}" == "YES" ]]; then
if [[ "${_DO_TUI^^}" == "YES" ]]; then
echo "${USERNAME} ALL=(ALL) NOPASSWD: ALL" \
| arch-chroot /mnt tee /etc/sudoers.d/99-setup-nopasswd > /dev/null
@ -227,10 +328,20 @@ if [[ "${_RUN_TUI^^}" == "YES" ]]; then
arch-chroot /mnt rm -f /etc/sudoers.d/99-setup-nopasswd
fi
# Remove answerfile from new system after setup completes
if $AF_MODE && [[ -f /mnt/answerfile.json ]]; then
rm -f /mnt/answerfile.json
fi
echo "Installation complete!"
echo " umount -R /mnt && reboot"
if [[ "${_RUN_TUI^^}" != "YES" ]]; then
if [[ "${_DO_TUI^^}" != "YES" ]]; then
echo
echo "After first boot, login as ${USERNAME} and run:"
echo " ~/Dotfiles/setup/tui-install.sh"
fi
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
echo
echo "LUKS backup key stored at /_LUKS_BACKUP_KEY in the new system."
echo "Keep this file secure — it can unlock the root partition."
fi

View File

@ -1,10 +1,48 @@
#!/usr/bin/env bash
# build.sh — build the M-Archy Arch Linux ISO
#
# Usage:
# bash build.sh [--preconf [FILE]] [OUT_DIR]
#
# --preconf Embed ~/answerfile.json into the ISO at /answerfile.json
# --preconf FILE Embed the specified answerfile instead
# OUT_DIR Output directory (default: ~/m-archy-out, or $OUT_DIR env)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DOTFILES_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
# ── Argument parsing ───────────────────────────────────────────────────────────
PRECONF_FILE=""
OUT_ARG=""
while [[ $# -gt 0 ]]; do
case "$1" in
--preconf)
# Optional next arg: a file path (doesn't start with -)
if [[ $# -gt 1 && "${2:0:1}" != "-" ]]; then
PRECONF_FILE="$2"; shift
else
PRECONF_FILE="$HOME/answerfile.json"
fi
shift
;;
--preconf=*)
PRECONF_FILE="${1#--preconf=}"
shift
;;
-*)
echo "Unknown flag: $1" >&2; exit 1
;;
*)
OUT_ARG="$1"; shift
;;
esac
done
WORK_DIR="${WORK_DIR:-$HOME/m-archy-build}"
OUT_DIR="${1:-${OUT_DIR:-$HOME/m-archy-out}}"
OUT_DIR="${OUT_ARG:-${OUT_DIR:-$HOME/m-archy-out}}"
PROFILE="$WORK_DIR/profile"
RELENG="/usr/share/archiso/configs/releng"
@ -15,6 +53,16 @@ fi
[[ -d "$RELENG" ]] || { echo "ERROR: $RELENG not found — is archiso installed?"; exit 1; }
# Validate answerfile early if --preconf was given
if [[ -n "$PRECONF_FILE" ]]; then
[[ -f "$PRECONF_FILE" ]] \
|| { echo "ERROR: answerfile not found: $PRECONF_FILE"; exit 1; }
command -v jq &>/dev/null \
&& jq empty "$PRECONF_FILE" \
|| echo "Warning: jq not available — skipping answerfile JSON validation"
echo "Answerfile to embed: $PRECONF_FILE"
fi
rm -rf "$WORK_DIR"
mkdir -p "$WORK_DIR" "$OUT_DIR"
@ -43,9 +91,19 @@ chmod 755 \
"$PROFILE/airootfs/usr/local/bin/install-arch" \
"$PROFILE/airootfs/root/installer/"*.sh
# ── Embed answerfile (--preconf) ───────────────────────────────────────────────
if [[ -n "$PRECONF_FILE" ]]; then
echo "Embedding answerfile: $PRECONF_FILE → /answerfile.json"
install -m 644 "$PRECONF_FILE" "$PROFILE/airootfs/answerfile.json"
fi
echo "Building ISO (this may take a while)..."
sudo mkarchiso -v -w "$WORK_DIR/mkarchiso" -o "$OUT_DIR" "$PROFILE"
echo
echo "Done."
ls -lh "$OUT_DIR/"*.iso 2>/dev/null || true
if [[ -n "$PRECONF_FILE" ]]; then
echo "Answerfile embedded — automated install will activate on boot."
fi

View File

@ -0,0 +1,377 @@
#!/bin/bash
# generate-answerfile.sh — dry-run the M-Archy installer dialogs and write
# all selections to an answerfile.json. Nothing is installed.
#
# Usage:
# bash generate-answerfile.sh [OUTPUT_PATH]
# OUTPUT_PATH defaults to ~/answerfile.json
#
# The generated file can be placed at /answerfile.json on the installer USB
# (use build.sh --preconf to embed it automatically) so that the full
# install — base OS + dotfiles — runs without any manual input.
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DOTFILES_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
OUTPUT="${1:-$HOME/answerfile.json}"
TMP_D="$(mktemp -d)"
trap 'rm -rf "$TMP_D"' EXIT
BACKTITLE="M-Archy Answerfile Generator"
# ── Dialog theme ──────────────────────────────────────────────────────────────
export DIALOGRC="$TMP_D/dialogrc"
cat > "$DIALOGRC" <<'EOF'
use_shadow = ON
use_colors = ON
screen_color = (BLACK,BLACK,ON)
shadow_color = (BLACK,BLACK,ON)
title_color = (MAGENTA,BLACK,ON)
border_color = (MAGENTA,BLACK,ON)
button_active_color = (BLACK,MAGENTA,ON)
button_inactive_color = (WHITE,BLACK,OFF)
button_key_active_color = (BLACK,CYAN,ON)
button_key_inactive_color = (CYAN,BLACK,ON)
button_label_active_color = (BLACK,MAGENTA,ON)
button_label_inactive_color = (WHITE,BLACK,OFF)
inputbox_color = (WHITE,BLACK,OFF)
inputbox_border_color = (MAGENTA,BLACK,ON)
menubox_color = (WHITE,BLACK,OFF)
menubox_border_color = (MAGENTA,BLACK,ON)
item_color = (WHITE,BLACK,OFF)
item_selected_color = (BLACK,MAGENTA,ON)
tag_color = (CYAN,BLACK,ON)
tag_selected_color = (BLACK,CYAN,ON)
tag_key_color = (CYAN,BLACK,ON)
tag_key_selected_color = (BLACK,CYAN,ON)
check_color = (WHITE,BLACK,OFF)
check_selected_color = (BLACK,MAGENTA,ON)
uarrow_color = (MAGENTA,BLACK,ON)
darrow_color = (MAGENTA,BLACK,ON)
EOF
# ── Helpers ───────────────────────────────────────────────────────────────────
require_dialog() {
command -v dialog &>/dev/null && return
echo "dialog not found — installing..."
sudo pacman -S --noconfirm dialog || { echo "Failed to install dialog."; exit 1; }
}
require_jq() {
command -v jq &>/dev/null && return
echo "jq not found — installing..."
sudo pacman -S --noconfirm jq || { echo "Failed to install jq."; exit 1; }
}
die() { clear; printf "\n Error: %s\n\n" "$1" >&2; exit 1; }
# json_str: emit a properly-quoted JSON string value (handles empty → null)
json_str() {
local v="$1"
if [[ -z "$v" ]]; then printf 'null'; else printf '"%s"' "$v"; fi
}
# json_bool: emit true/false from YES/NO or true/false input
json_bool() {
local v="${1,,}"
[[ "$v" == "yes" || "$v" == "true" ]] && echo "true" || echo "false"
}
# ── Preflight ─────────────────────────────────────────────────────────────────
require_dialog
require_jq
# ── Welcome ───────────────────────────────────────────────────────────────────
dialog --backtitle "$BACKTITLE" \
--title " Answerfile Generator " \
--msgbox "\n\
This tool walks you through all installer dialogs\n\
and saves your choices to an answerfile.\n\
─────────────────────────────────────────────────\n\
\n\
NO software will be installed — this is a dry run.\n\
\n\
Output: $OUTPUT\n\
\n\
To use on a USB: build.sh --preconf $OUTPUT\n" 16 62
# ═══════════════════════════════════════════════════════════════
# PART 1 — Base OS install options
# ═══════════════════════════════════════════════════════════════
# ── Drive ─────────────────────────────────────────────────────────────────────
AVAIL_DRIVES=$(lsblk -dn -o NAME,SIZE,MODEL 2>/dev/null | awk '{printf "%s \"%s %s\" off ", $1, $2, $3}' || true)
AF_DRIVE=$(dialog --backtitle "$BACKTITLE" \
--title " Target Drive " \
--inputbox "\n Enter the install drive (e.g. /dev/sda, /dev/nvme0n1).\n\n Available drives:\n$(lsblk -dn -o NAME,SIZE,MODEL 2>/dev/null | sed 's/^/ /')\n" \
16 64 "/dev/sda" \
3>&1 1>&2 2>&3) || AF_DRIVE=""
# ── 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" \
3>&1 1>&2 2>&3) || AF_KERNEL="linux"
# ── Hostname ──────────────────────────────────────────────────────────────────
AF_HOSTNAME=$(dialog --backtitle "$BACKTITLE" \
--title " Hostname " \
--inputbox "\n Hostname for the new system.\n\n Leave blank to keep default.\n\n Note: a MAC address suffix will be appended\n automatically when the answerfile is applied,\n preventing hostname conflicts across machines.\n" \
14 62 "" \
3>&1 1>&2 2>&3) || AF_HOSTNAME=""
# ── Username ──────────────────────────────────────────────────────────────────
AF_USERNAME=$(dialog --backtitle "$BACKTITLE" \
--title " Username " \
--inputbox "\n Name for the primary user account.\n" \
9 54 "" \
3>&1 1>&2 2>&3) || AF_USERNAME=""
# NOTE: passwords are intentionally NOT stored in the answerfile.
dialog --backtitle "$BACKTITLE" \
--title " Password " \
--msgbox "\n Passwords are NOT stored in the answerfile.\n\n You will be prompted for the user password\n at install time even in automated mode.\n" \
10 56
# ── Disk encryption ───────────────────────────────────────────────────────────
dialog --backtitle "$BACKTITLE" \
--title " Disk Encryption " \
--yesno "\n Enable LUKS2 disk encryption on the root partition?\n\n If yes, a backup LUKS key will be auto-generated\n and placed at /_LUKS_BACKUP_KEY in the new system.\n" \
11 62 && AF_ENCRYPT="true" || AF_ENCRYPT="false"
AF_FIDO2_ROOT="false"
AF_FIDO2_USER="false"
if [[ "$AF_ENCRYPT" == "true" ]]; then
dialog --backtitle "$BACKTITLE" \
--title " FIDO2 Root Unlock " \
--yesno "\n Enable FIDO2 hardware key for LUKS root unlock?\n" \
8 56 && AF_FIDO2_ROOT="true" || AF_FIDO2_ROOT="false"
fi
dialog --backtitle "$BACKTITLE" \
--title " FIDO2 User Login " \
--yesno "\n Enable FIDO2 hardware key for user login (PAM)?\n" \
8 56 && AF_FIDO2_USER="true" || AF_FIDO2_USER="false"
# ── Run TUI installer after base install ──────────────────────────────────────
dialog --backtitle "$BACKTITLE" \
--title " Dotfiles Setup " \
--yesno "\n Automatically run the dotfiles TUI installer\n inside the chroot after base install completes?\n" \
9 58 && AF_RUN_TUI="true" || AF_RUN_TUI="false"
# ═══════════════════════════════════════════════════════════════
# PART 2 — Dotfiles / TUI installer options
# (only shown if run_tui is true)
# ═══════════════════════════════════════════════════════════════
AF_COMPONENTS=""
AF_DE="none"
AF_APPS=""
AF_COLOR_TEXT=""
AF_COLOR_BG=""
AF_COLOR_HIGHLIGHT=""
AF_COLOR_DARK=""
AF_COLOR_RED=""
if [[ "$AF_RUN_TUI" == "true" ]]; then
# ── Components ────────────────────────────────────────────────────────────
AF_COMPONENTS=$(dialog --backtitle "$BACKTITLE" \
--title " Select Components " \
--checklist "Space toggles · Enter confirms · Esc quits" 15 68 4 \
"pkg" "Package managers yay · nvm · rust" on \
"core" "Core packages 100+ base system packages" on \
"svc" "Core services NetworkManager · cronie · fail2ban" on \
"shell" "Shell setup zsh · nvim · yazi · micro · starship" on \
3>&1 1>&2 2>&3) || AF_COMPONENTS=""
# ── DE ────────────────────────────────────────────────────────────────────
AF_DE=$(dialog --backtitle "$BACKTITLE" \
--title " Desktop Environment " \
--menu "Select a desktop environment · Esc / none to skip:" 20 70 8 \
"hyprland" "Hyprland — Wayland WM, full setup (primary)" \
"sway" "Sway — Wayland tiling WM" \
"kde-plasma" "KDE Plasma — feature-rich Wayland/X11 DE" \
"gnome" "GNOME — modern Wayland DE" \
"cosmic" "COSMIC — Rust-built Wayland DE (System76)" \
"xfce" "XFCE — lightweight X11 DE" \
"lxqt" "LXQt — lightweight Qt X11 DE" \
"none" "Skip DE installation" \
3>&1 1>&2 2>&3) || AF_DE="none"
# ── Apps ──────────────────────────────────────────────────────────────────
AF_APPS=$(dialog --backtitle "$BACKTITLE" \
--title " Applications " \
--checklist "Optional applications — installed after base components:" 40 76 32 \
"ollama" "Ollama local LLM runner + API server" off \
"llama-cpp" "llama.cpp standalone inference CLI + server" off \
"open-webui" "Open WebUI browser UI for Ollama / LLM backends" off \
"claude" "Claude Code Anthropic CLI via npm" off \
"networking-cli" "Networking CLI nmap · nethogs · mitmproxy · httpie" off \
"disk-recovery" "Disk Recovery ddrescue · f3" off \
"himalaya" "Himalaya terminal email client (AUR)" off \
"gnuplot" "Gnuplot scientific plotting" off \
"povray" "POV-Ray ray-tracing renderer" off \
"blender" "Blender 3D creation suite" off \
"toot" "toot Mastodon CLI client (AUR)" off \
"db-clients" "DB Clients pgcli · mycli" off \
"mysql" "MySQL / MariaDB mariadb server + setup" off \
"productivity" "Productivity taskwarrior · watson · jrnl" off \
"yt-dlp" "yt-dlp YouTube / media downloader" off \
"sox" "SoX audio processing toolkit" off \
"imagemagick" "ImageMagick image manipulation" off \
"ffmpeg" "FFmpeg extras thumbnailer · GStreamer codecs" off \
"localtunnel" "LocalTunnel expose localhost via tunnel" off \
"butter" "butter btrfs snapshot backup (AUR)" off \
"tlp" "TLP laptop power management" off \
"steam" "Steam gaming platform" off \
"vesktop" "Vesktop Discord + Vencord theme" off \
"spotify" "Spotify launcher + Spicetify theming" off \
"prism" "PrismLauncher Minecraft launcher (Flatpak)" off \
"vintagestory" "Vintage Story survival game (AUR)" off \
"localsend" "LocalSend LAN file transfer (AUR)" off \
"croc" "croc cross-platform file transfer" off \
"onlyoffice" "OnlyOffice office suite (AUR)" off \
"wireshark" "Wireshark network packet analyser (GUI)" off \
"k8s" "Kubernetes tools kubectl · podman-desktop" off \
"docker" "Docker docker · docker-compose" off \
"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 \
"zfs" "ZFS zfs-dkms kernel module" off \
"wprs" "WPRS wprs-git (AUR)" off \
"chromium" "Chromium open-source browser (official)" off \
"firefox-browser" "Firefox Mozilla browser (official)" off \
"zen-browser" "Zen Browser Firefox-based privacy browser (AUR)" off \
"nyxt" "Nyxt keyboard-driven browser (AUR)" off \
"librewolf" "LibreWolf hardened Firefox fork (AUR)" off \
"min-browser" "Min minimal Electron browser (AUR)" off \
"vscodium" "VSCodium telemetry-free VS Code (AUR)" off \
"zed-ide" "Zed high-performance Rust IDE (official)" off \
"geany" "Geany lightweight IDE + plugins (official)" off \
"codeblocks" "Code::Blocks C/C++ IDE (official)" off \
"kate" "Kate KDE advanced text editor (official)" off \
3>&1 1>&2 2>&3) || AF_APPS=""
# ── Colorway ──────────────────────────────────────────────────────────────
# Read defaults from repo colors.conf
declare -A _cdef
if [[ -f "$DOTFILES_DIR/colors.conf" ]]; then
while IFS='=' read -r k v; do
k="${k%%[[:space:]]*}"
[[ "$k" =~ ^[[:space:]]*# || -z "$k" ]] && continue
v="${v%%#*}"; v="${v//[[:space:]]/}"; v="${v^^}"
_cdef[$k]="$v"
done < "$DOTFILES_DIR/colors.conf"
fi
DEF_TEXT="${_cdef[COLOR_TEXT]:-D6ABAB}"
DEF_BG="${_cdef[COLOR_BG]:-1A1A1A}"
DEF_HIGHLIGHT="${_cdef[COLOR_HIGHLIGHT]:-E40046}"
DEF_DARK="${_cdef[COLOR_DARK]:-5018DD}"
DEF_RED="${_cdef[COLOR_RED]:-F50505}"
COLORWAY_RAW=$(dialog --backtitle "$BACKTITLE" \
--title " Colorway (optional) " \
--form "\n Customize theme colors — bare 6-digit hex, no #.\n Leave unchanged to omit colors from answerfile.\n" \
16 62 5 \
"COLOR_TEXT " 1 1 "$DEF_TEXT" 1 18 10 7 \
"COLOR_BG " 2 1 "$DEF_BG" 2 18 10 7 \
"COLOR_HIGHLIGHT " 3 1 "$DEF_HIGHLIGHT" 3 18 10 7 \
"COLOR_DARK " 4 1 "$DEF_DARK" 4 18 10 7 \
"COLOR_RED " 5 1 "$DEF_RED" 5 18 10 7 \
3>&1 1>&2 2>&3) || COLORWAY_RAW=""
if [[ -n "$COLORWAY_RAW" ]]; then
mapfile -t _cv <<< "$COLORWAY_RAW"
N_TEXT="${_cv[0]:-$DEF_TEXT}"
N_BG="${_cv[1]:-$DEF_BG}"
N_HIGHLIGHT="${_cv[2]:-$DEF_HIGHLIGHT}"
N_DARK="${_cv[3]:-$DEF_DARK}"
N_RED="${_cv[4]:-$DEF_RED}"
# Only save colors if any differ from defaults
if [[ "${N_TEXT^^}" != "$DEF_TEXT" || \
"${N_BG^^}" != "$DEF_BG" || \
"${N_HIGHLIGHT^^}" != "$DEF_HIGHLIGHT" || \
"${N_DARK^^}" != "$DEF_DARK" || \
"${N_RED^^}" != "$DEF_RED" ]]; then
AF_COLOR_TEXT="${N_TEXT^^}"
AF_COLOR_BG="${N_BG^^}"
AF_COLOR_HIGHLIGHT="${N_HIGHLIGHT^^}"
AF_COLOR_DARK="${N_DARK^^}"
AF_COLOR_RED="${N_RED^^}"
fi
fi
fi
# ── Confirmation ──────────────────────────────────────────────────────────────
SUMMARY=""
[[ -n "$AF_DRIVE" ]] && SUMMARY+=" Drive: $AF_DRIVE\n"
[[ -n "$AF_KERNEL" ]] && SUMMARY+=" Kernel: $AF_KERNEL\n"
[[ -n "$AF_HOSTNAME" ]] && SUMMARY+=" Hostname: $AF_HOSTNAME (+ MAC suffix at deploy)\n"
[[ -n "$AF_USERNAME" ]] && SUMMARY+=" Username: $AF_USERNAME\n"
SUMMARY+=" Encrypt: $AF_ENCRYPT\n"
SUMMARY+=" FIDO2 root: $AF_FIDO2_ROOT / FIDO2 user: $AF_FIDO2_USER\n"
SUMMARY+=" Run TUI: $AF_RUN_TUI\n"
[[ -n "$AF_DE" && "$AF_DE" != "none" ]] && SUMMARY+=" DE: $AF_DE\n"
[[ -n "$AF_COLOR_TEXT" ]] && SUMMARY+=" Colors: custom\n"
dialog --backtitle "$BACKTITLE" \
--title " Confirm " \
--yesno "\n Save answerfile with these settings:\n\n${SUMMARY}\n Output: $OUTPUT\n\n Proceed?" \
22 66 || { clear; echo "Aborted."; exit 0; }
# ── Build JSON arrays from space-separated dialog output ──────────────────────
_words_to_json_array() {
local input="$1"
local first=1
printf '['
for w in $input; do
[[ $first -eq 0 ]] && printf ','
printf '"%s"' "$w"
first=0
done
printf ']'
}
# ── Write answerfile ──────────────────────────────────────────────────────────
mkdir -p "$(dirname "$OUTPUT")"
{
printf '{\n'
printf ' "_generated": "%s",\n' "$(date -Iseconds)"
printf ' "drive": %s,\n' "$(json_str "$AF_DRIVE")"
printf ' "kernel": %s,\n' "$(json_str "$AF_KERNEL")"
printf ' "hostname": %s,\n' "$(json_str "$AF_HOSTNAME")"
printf ' "username": %s,\n' "$(json_str "$AF_USERNAME")"
printf ' "encrypt": %s,\n' "$AF_ENCRYPT"
printf ' "fido2_root": %s,\n' "$AF_FIDO2_ROOT"
printf ' "fido2_user": %s,\n' "$AF_FIDO2_USER"
printf ' "run_tui": %s,\n' "$AF_RUN_TUI"
printf ' "components": %s,\n' "$(_words_to_json_array "$AF_COMPONENTS")"
printf ' "desktop_environment": %s,\n' "$(json_str "$AF_DE")"
printf ' "apps": %s' "$(_words_to_json_array "$AF_APPS")"
if [[ -n "$AF_COLOR_TEXT" ]]; then
printf ',\n "colors": {\n'
printf ' "COLOR_TEXT": "%s",\n' "$AF_COLOR_TEXT"
printf ' "COLOR_BG": "%s",\n' "$AF_COLOR_BG"
printf ' "COLOR_HIGHLIGHT": "%s",\n' "$AF_COLOR_HIGHLIGHT"
printf ' "COLOR_DARK": "%s",\n' "$AF_COLOR_DARK"
printf ' "COLOR_RED": "%s"\n' "$AF_COLOR_RED"
printf ' }'
fi
printf '\n}\n'
} > "$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"

View File

@ -0,0 +1,69 @@
---
# collect-luks-keys.yml — fetch LUKS backup keys from enrolled clients.
#
# When a client was installed with disk encryption via the M-Archy installer,
# a backup LUKS key is stored at /_LUKS_BACKUP_KEY inside the encrypted root.
# This playbook fetches those keys to the controller and names each copy
# <HOSTNAME>_LUKS_BACKUP_KEY so they can be archived securely.
#
# Keys are stored in luks-keys/ relative to the playbook directory.
# Protect that directory carefully — keys can unlock client root partitions.
#
# Usage:
# ansible-playbook -i inventory collect-luks-keys.yml
# ansible-playbook -i inventory collect-luks-keys.yml -e luks_keys_store=/secure/path
#
# To run automatically, add a cron job on the Ansible controller:
# 0 3 * * * cd /path/to/playbooks && ansible-playbook -i inventory collect-luks-keys.yml
- name: Collect LUKS backup keys from enrolled clients
hosts: all
become: yes
vars:
luks_key_path: /_LUKS_BACKUP_KEY
luks_keys_store: "{{ playbook_dir }}/luks-keys"
tasks:
- name: Ensure local key store directory exists
file:
path: "{{ luks_keys_store }}"
state: directory
mode: '0700'
delegate_to: localhost
run_once: true
become: false
- name: Check for LUKS backup key on client
stat:
path: "{{ luks_key_path }}"
register: luks_key_stat
- name: Fetch LUKS backup key to controller
fetch:
src: "{{ luks_key_path }}"
dest: "{{ luks_keys_store }}/{{ inventory_hostname }}_LUKS_BACKUP_KEY"
flat: yes
when: luks_key_stat.stat.exists
register: luks_key_fetch
- name: Secure fetched key permissions
file:
path: "{{ luks_keys_store }}/{{ inventory_hostname }}_LUKS_BACKUP_KEY"
mode: '0400'
delegate_to: localhost
become: false
when:
- luks_key_stat.stat.exists
- luks_key_fetch is changed
- name: Report key status
debug:
msg: >-
{{ inventory_hostname }}:
{% if luks_key_stat.stat.exists %}
key found and fetched to {{ luks_keys_store }}/{{ inventory_hostname }}_LUKS_BACKUP_KEY
{% else %}
no /_LUKS_BACKUP_KEY present (unencrypted or already collected)
{% endif %}

View File

@ -12,6 +12,10 @@ LOG="$HOME/dotfiles-install.log"
TMP_D="$(mktemp -d)"
trap 'rm -rf "$TMP_D"' EXIT
ANSWERFILE="${ANSWERFILE:-/answerfile.json}"
ANSWERFILE_MODE=false
[[ -f "$ANSWERFILE" ]] && ANSWERFILE_MODE=true
BACKTITLE="the_miro's Arch Dotfiles"
# ── Cyberqueer dialog theme ───────────────────────────────────────────────────
@ -56,6 +60,12 @@ require_dialog() {
sudo pacman -S --noconfirm dialog || { echo "Failed to install dialog."; exit 1; }
}
require_jq() {
command -v jq &>/dev/null && return
echo "jq not found — installing..."
sudo pacman -S --noconfirm jq || { echo "Failed to install jq."; exit 1; }
}
die() {
clear
printf "\n Error: %s\n\n" "$1" >&2
@ -79,10 +89,14 @@ run_module() {
bash "$script" 2>&1 | tee -a "$LOG" || rc=${PIPESTATUS[0]}
if [[ $rc -ne 0 ]]; then
dialog --backtitle "$BACKTITLE" \
--title " Module Failed " \
--yesno "$label exited with code $rc.\n\nContinue anyway?" 8 54 \
|| { clear; exit 1; }
if [[ $ANSWERFILE_MODE == true ]]; then
printf "\n Warning: %s exited with code %d — continuing.\n" "$label" "$rc" | tee -a "$LOG"
else
dialog --backtitle "$BACKTITLE" \
--title " Module Failed " \
--yesno "$label exited with code $rc.\n\nContinue anyway?" 8 54 \
|| { clear; exit 1; }
fi
fi
}
@ -148,22 +162,63 @@ count_steps() {
[[ "$a" == *"kate"* ]] && TOTAL=$(( TOTAL + 1 ))
}
# ── Answerfile ────────────────────────────────────────────────────────────────
AF_HOSTNAME=""
AF_COMPONENTS=""
AF_DE="none"
AF_APPS=""
AF_COLOR_TEXT=""
AF_COLOR_BG=""
AF_COLOR_HIGHLIGHT=""
AF_COLOR_DARK=""
AF_COLOR_RED=""
load_answerfile() {
require_jq
AF_HOSTNAME=$(jq -r '.hostname // ""' "$ANSWERFILE")
AF_COMPONENTS=$(jq -r '(.components // []) | join(" ")' "$ANSWERFILE")
AF_DE=$(jq -r '.desktop_environment // "none"' "$ANSWERFILE")
AF_APPS=$(jq -r '(.apps // []) | join(" ")' "$ANSWERFILE")
AF_COLOR_TEXT=$(jq -r '.colors.COLOR_TEXT // ""' "$ANSWERFILE")
AF_COLOR_BG=$(jq -r '.colors.COLOR_BG // ""' "$ANSWERFILE")
AF_COLOR_HIGHLIGHT=$(jq -r '.colors.COLOR_HIGHLIGHT // ""' "$ANSWERFILE")
AF_COLOR_DARK=$(jq -r '.colors.COLOR_DARK // ""' "$ANSWERFILE")
AF_COLOR_RED=$(jq -r '.colors.COLOR_RED // ""' "$ANSWERFILE")
}
# ── MAC address helper ────────────────────────────────────────────────────────
get_mac_suffix() {
local mac
mac=$(ip link show 2>/dev/null \
| awk '/^[0-9]+: [^l][^o]/{iface=1} iface && /link\/ether/{print $2; iface=0; exit}')
printf '%s' "${mac//:/}"
}
# ── Preflight ─────────────────────────────────────────────────────────────────
[[ $EUID -eq 0 ]] && die "Run as your normal user (not root)."
command -v pacman &>/dev/null || die "pacman not found — Arch Linux required."
require_dialog
if $ANSWERFILE_MODE; then
load_answerfile
printf "Answerfile mode: %s\n" "$ANSWERFILE" | tee -a "$LOG"
fi
if ! ping -c1 -W3 archlinux.org &>/dev/null; then
dialog --backtitle "$BACKTITLE" \
--title " No Network Detected " \
--msgbox "\n No internet connection found.\n\n nmtui will open so you can configure networking.\n Close nmtui when done to continue the installer.\n" 11 58
nmtui
if ! ping -c1 -W3 archlinux.org &>/dev/null; then
if $ANSWERFILE_MODE; then
printf "Warning: no internet connection detected.\n" | tee -a "$LOG"
else
dialog --backtitle "$BACKTITLE" \
--title " Still Offline " \
--yesno "\n Still no internet connection.\n\n Packages cannot be downloaded without network access.\n\n Continue anyway?" 11 58 \
|| { clear; echo "Aborted — no network."; exit 1; }
--title " No Network Detected " \
--msgbox "\n No internet connection found.\n\n nmtui will open so you can configure networking.\n Close nmtui when done to continue the installer.\n" 11 58
nmtui
if ! ping -c1 -W3 archlinux.org &>/dev/null; then
dialog --backtitle "$BACKTITLE" \
--title " Still Offline " \
--yesno "\n Still no internet connection.\n\n Packages cannot be downloaded without network access.\n\n Continue anyway?" 11 58 \
|| { clear; echo "Aborted — no network."; exit 1; }
fi
fi
fi
@ -171,9 +226,10 @@ fi
printf "Dotfiles install: %s\nDotfiles dir: %s\n" "$(date)" "$DOTFILES_DIR" >> "$LOG"
# ── Welcome ───────────────────────────────────────────────────────────────────
dialog --backtitle "$BACKTITLE" \
--title " Welcome " \
--msgbox "\n\
if ! $ANSWERFILE_MODE; then
dialog --backtitle "$BACKTITLE" \
--title " Welcome " \
--msgbox "\n\
the_miro's Arch dotfiles installer\n\
Cyberqueer · Wayland · Hyprland\n\
─────────────────────────────────────────\n\
@ -182,159 +238,197 @@ dialog --backtitle "$BACKTITLE" \
\n\
Source: $DOTFILES_DIR\n\
Log: $LOG\n" 14 62
# ── Component selection ───────────────────────────────────────────────────────
COMPONENTS=$(dialog --backtitle "$BACKTITLE" \
--title " Select Components " \
--checklist "Space toggles · Enter confirms · Esc quits" 15 68 4 \
"pkg" "Package managers yay · nvm · rust" on \
"core" "Core packages 100+ base system packages" on \
"svc" "Core services NetworkManager · cronie · fail2ban" on \
"shell" "Shell setup zsh · nvim · yazi · micro · starship" on \
3>&1 1>&2 2>&3) || { clear; echo "Aborted."; exit 0; }
# ── DE selection ──────────────────────────────────────────────────────────────
DE=$(dialog --backtitle "$BACKTITLE" \
--title " Desktop Environment " \
--menu "Select a desktop environment · Esc / none to skip:" 20 70 8 \
"hyprland" "Hyprland — Wayland WM, full setup (primary)" \
"sway" "Sway — Wayland tiling WM" \
"kde-plasma" "KDE Plasma — feature-rich Wayland/X11 DE" \
"gnome" "GNOME — modern Wayland DE" \
"cosmic" "COSMIC — Rust-built Wayland DE (System76)" \
"xfce" "XFCE — lightweight X11 DE" \
"lxqt" "LXQt — lightweight Qt X11 DE" \
"none" "Skip DE installation" \
3>&1 1>&2 2>&3) || DE="none"
# ── Apps selection ────────────────────────────────────────────────────────────
SELECTED_APPS=$(dialog --backtitle "$BACKTITLE" \
--title " Applications " \
--checklist "Optional applications — installed after base components:" 40 76 32 \
"ollama" "Ollama local LLM runner + API server" off \
"llama-cpp" "llama.cpp standalone inference CLI + server" off \
"open-webui" "Open WebUI browser UI for Ollama / LLM backends" off \
"claude" "Claude Code Anthropic CLI via npm" off \
"networking-cli" "Networking CLI nmap · nethogs · mitmproxy · httpie" off \
"disk-recovery" "Disk Recovery ddrescue · f3" off \
"himalaya" "Himalaya terminal email client (AUR)" off \
"gnuplot" "Gnuplot scientific plotting" off \
"povray" "POV-Ray ray-tracing renderer" off \
"blender" "Blender 3D creation suite" off \
"toot" "toot Mastodon CLI client (AUR)" off \
"db-clients" "DB Clients pgcli · mycli" off \
"mysql" "MySQL / MariaDB mariadb server + setup" off \
"productivity" "Productivity taskwarrior · watson · jrnl" off \
"yt-dlp" "yt-dlp YouTube / media downloader" off \
"sox" "SoX audio processing toolkit" off \
"imagemagick" "ImageMagick image manipulation" off \
"ffmpeg" "FFmpeg extras thumbnailer · GStreamer codecs" off \
"localtunnel" "LocalTunnel expose localhost via tunnel" off \
"butter" "butter btrfs snapshot backup (AUR)" off \
"tlp" "TLP laptop power management" off \
"steam" "Steam gaming platform" off \
"vesktop" "Vesktop Discord + Vencord theme" off \
"spotify" "Spotify launcher + Spicetify theming" off \
"prism" "PrismLauncher Minecraft launcher (Flatpak)" off \
"vintagestory" "Vintage Story survival game (AUR)" off \
"localsend" "LocalSend LAN file transfer (AUR)" off \
"croc" "croc cross-platform file transfer" off \
"onlyoffice" "OnlyOffice office suite (AUR)" off \
"wireshark" "Wireshark network packet analyser (GUI)" off \
"k8s" "Kubernetes tools kubectl · podman-desktop" off \
"docker" "Docker docker · docker-compose" off \
"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 \
"zfs" "ZFS zfs-dkms kernel module" off \
"wprs" "WPRS wprs-git (AUR)" off \
\
"chromium" "Chromium open-source browser (official)" off \
"firefox-browser" "Firefox Mozilla browser (official)" off \
"zen-browser" "Zen Browser Firefox-based privacy browser (AUR)" off \
"nyxt" "Nyxt keyboard-driven browser (AUR)" off \
"librewolf" "LibreWolf hardened Firefox fork (AUR)" off \
"min-browser" "Min minimal Electron browser (AUR)" off \
\
"vscodium" "VSCodium telemetry-free VS Code (AUR)" off \
"zed-ide" "Zed high-performance Rust IDE (official)" off \
"geany" "Geany lightweight IDE + plugins (official)" off \
"codeblocks" "Code::Blocks C/C++ IDE (official)" off \
"kate" "Kate KDE advanced text editor (official)" off \
3>&1 1>&2 2>&3) || SELECTED_APPS=""
# ── Confirmation ──────────────────────────────────────────────────────────────
SUMMARY=""
[[ "$COMPONENTS" == *"pkg"* ]] && SUMMARY+=" ✦ Package managers (yay, nvm, rust)\n"
[[ "$COMPONENTS" == *"core"* ]] && SUMMARY+=" ✦ Core packages\n"
[[ "$COMPONENTS" == *"svc"* ]] && SUMMARY+=" ✦ Core services\n"
[[ "$COMPONENTS" == *"shell"* ]] && SUMMARY+=" ✦ Shell setup\n"
[[ "$DE" != "none" ]] && SUMMARY+=" ✦ Desktop environment: $DE\n"
if [[ -n "$SELECTED_APPS" ]]; then
SUMMARY+="\n Applications:\n"
[[ "$SELECTED_APPS" == *"ollama"* ]] && SUMMARY+=" ✦ Ollama\n"
[[ "$SELECTED_APPS" == *"llama-cpp"* ]] && SUMMARY+=" ✦ llama.cpp\n"
[[ "$SELECTED_APPS" == *"open-webui"* ]] && SUMMARY+=" ✦ Open WebUI\n"
[[ "$SELECTED_APPS" == *"claude"* ]] && SUMMARY+=" ✦ Claude Code\n"
[[ "$SELECTED_APPS" == *"networking-cli"* ]] && SUMMARY+=" ✦ Networking CLI (nmap, nethogs, mitmproxy, httpie)\n"
[[ "$SELECTED_APPS" == *"disk-recovery"* ]] && SUMMARY+=" ✦ Disk Recovery (ddrescue, f3)\n"
[[ "$SELECTED_APPS" == *"himalaya"* ]] && SUMMARY+=" ✦ Himalaya\n"
[[ "$SELECTED_APPS" == *"gnuplot"* ]] && SUMMARY+=" ✦ Gnuplot\n"
[[ "$SELECTED_APPS" == *"povray"* ]] && SUMMARY+=" ✦ POV-Ray\n"
[[ "$SELECTED_APPS" == *"blender"* ]] && SUMMARY+=" ✦ Blender\n"
[[ "$SELECTED_APPS" == *"toot"* ]] && SUMMARY+=" ✦ toot\n"
[[ "$SELECTED_APPS" == *"db-clients"* ]] && SUMMARY+=" ✦ DB Clients (pgcli, mycli)\n"
[[ "$SELECTED_APPS" == *"mysql"* ]] && SUMMARY+=" ✦ MySQL / MariaDB\n"
[[ "$SELECTED_APPS" == *"productivity"* ]] && SUMMARY+=" ✦ Productivity (taskwarrior, watson, jrnl)\n"
[[ "$SELECTED_APPS" == *"yt-dlp"* ]] && SUMMARY+=" ✦ yt-dlp\n"
[[ "$SELECTED_APPS" == *"sox"* ]] && SUMMARY+=" ✦ SoX\n"
[[ "$SELECTED_APPS" == *"imagemagick"* ]] && SUMMARY+=" ✦ ImageMagick\n"
[[ "$SELECTED_APPS" == *"ffmpeg"* ]] && SUMMARY+=" ✦ FFmpeg extras\n"
[[ "$SELECTED_APPS" == *"localtunnel"* ]] && SUMMARY+=" ✦ LocalTunnel\n"
[[ "$SELECTED_APPS" == *"butter"* ]] && SUMMARY+=" ✦ butter (btrfs backup)\n"
[[ "$SELECTED_APPS" == *"tlp"* ]] && SUMMARY+=" ✦ TLP\n"
[[ "$SELECTED_APPS" == *"steam"* ]] && SUMMARY+=" ✦ Steam\n"
[[ "$SELECTED_APPS" == *"vesktop"* ]] && SUMMARY+=" ✦ Vesktop + Vencord theme\n"
[[ "$SELECTED_APPS" == *"spotify"* ]] && SUMMARY+=" ✦ Spotify + Spicetify\n"
[[ "$SELECTED_APPS" == *"prism"* ]] && SUMMARY+=" ✦ PrismLauncher\n"
[[ "$SELECTED_APPS" == *"vintagestory"* ]] && SUMMARY+=" ✦ Vintage Story\n"
[[ "$SELECTED_APPS" == *"localsend"* ]] && SUMMARY+=" ✦ LocalSend\n"
[[ "$SELECTED_APPS" == *"croc"* ]] && SUMMARY+=" ✦ croc\n"
[[ "$SELECTED_APPS" == *"onlyoffice"* ]] && SUMMARY+=" ✦ OnlyOffice\n"
[[ "$SELECTED_APPS" == *"wireshark"* ]] && SUMMARY+=" ✦ Wireshark\n"
[[ "$SELECTED_APPS" == *"k8s"* ]] && SUMMARY+=" ✦ Kubernetes tools\n"
[[ "$SELECTED_APPS" == *"docker"* ]] && SUMMARY+=" ✦ Docker + Compose\n"
[[ "$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"
[[ "$SELECTED_APPS" == *"zfs"* ]] && SUMMARY+=" ✦ ZFS\n"
[[ "$SELECTED_APPS" == *"wprs"* ]] && SUMMARY+=" ✦ WPRS\n"
[[ "$SELECTED_APPS" == *"chromium"* ]] && SUMMARY+=" ✦ Chromium\n"
[[ "$SELECTED_APPS" == *"firefox-browser"* ]] && SUMMARY+=" ✦ Firefox\n"
[[ "$SELECTED_APPS" == *"zen-browser"* ]] && SUMMARY+=" ✦ Zen Browser\n"
[[ "$SELECTED_APPS" == *"nyxt"* ]] && SUMMARY+=" ✦ Nyxt\n"
[[ "$SELECTED_APPS" == *"librewolf"* ]] && SUMMARY+=" ✦ LibreWolf\n"
[[ "$SELECTED_APPS" == *"min-browser"* ]] && SUMMARY+=" ✦ Min Browser\n"
[[ "$SELECTED_APPS" == *"vscodium"* ]] && SUMMARY+=" ✦ VSCodium\n"
[[ "$SELECTED_APPS" == *"zed-ide"* ]] && SUMMARY+=" ✦ Zed IDE\n"
[[ "$SELECTED_APPS" == *"geany"* ]] && SUMMARY+=" ✦ Geany\n"
[[ "$SELECTED_APPS" == *"codeblocks"* ]] && SUMMARY+=" ✦ Code::Blocks\n"
[[ "$SELECTED_APPS" == *"kate"* ]] && SUMMARY+=" ✦ Kate\n"
fi
dialog --backtitle "$BACKTITLE" \
--title " Confirm Installation " \
--yesno "\n Components to install:\n\n${SUMMARY}\n Log: $LOG\n\n Proceed?" \
24 62 || { clear; echo "Aborted."; exit 0; }
# ── Hostname ──────────────────────────────────────────────────────────────────
HOSTNAME_SET=""
if $ANSWERFILE_MODE; then
if [[ -n "$AF_HOSTNAME" ]]; then
MAC=$(get_mac_suffix)
HOSTNAME_SET="${AF_HOSTNAME}-${MAC}"
printf "Hostname (from answerfile + MAC): %s\n" "$HOSTNAME_SET" | tee -a "$LOG"
fi
else
HOSTNAME_INPUT=$(dialog --backtitle "$BACKTITLE" \
--title " Hostname " \
--inputbox "\n Hostname for this machine (leave blank to keep default).\n" 9 54 "" \
3>&1 1>&2 2>&3) || HOSTNAME_INPUT=""
HOSTNAME_SET="$HOSTNAME_INPUT"
fi
if [[ -n "$HOSTNAME_SET" ]]; then
sudo hostnamectl set-hostname "$HOSTNAME_SET" 2>/dev/null \
|| echo "$HOSTNAME_SET" | sudo tee /etc/hostname > /dev/null
printf "Hostname set: %s\n" "$HOSTNAME_SET" >> "$LOG"
fi
# ── Component selection ───────────────────────────────────────────────────────
if $ANSWERFILE_MODE; then
COMPONENTS="$AF_COMPONENTS"
else
COMPONENTS=$(dialog --backtitle "$BACKTITLE" \
--title " Select Components " \
--checklist "Space toggles · Enter confirms · Esc quits" 15 68 4 \
"pkg" "Package managers yay · nvm · rust" on \
"core" "Core packages 100+ base system packages" on \
"svc" "Core services NetworkManager · cronie · fail2ban" on \
"shell" "Shell setup zsh · nvim · yazi · micro · starship" on \
3>&1 1>&2 2>&3) || { clear; echo "Aborted."; exit 0; }
fi
# ── DE selection ──────────────────────────────────────────────────────────────
if $ANSWERFILE_MODE; then
DE="$AF_DE"
else
DE=$(dialog --backtitle "$BACKTITLE" \
--title " Desktop Environment " \
--menu "Select a desktop environment · Esc / none to skip:" 20 70 8 \
"hyprland" "Hyprland — Wayland WM, full setup (primary)" \
"sway" "Sway — Wayland tiling WM" \
"kde-plasma" "KDE Plasma — feature-rich Wayland/X11 DE" \
"gnome" "GNOME — modern Wayland DE" \
"cosmic" "COSMIC — Rust-built Wayland DE (System76)" \
"xfce" "XFCE — lightweight X11 DE" \
"lxqt" "LXQt — lightweight Qt X11 DE" \
"none" "Skip DE installation" \
3>&1 1>&2 2>&3) || DE="none"
fi
# ── Apps selection ────────────────────────────────────────────────────────────
if $ANSWERFILE_MODE; then
SELECTED_APPS="$AF_APPS"
else
SELECTED_APPS=$(dialog --backtitle "$BACKTITLE" \
--title " Applications " \
--checklist "Optional applications — installed after base components:" 40 76 32 \
"ollama" "Ollama local LLM runner + API server" off \
"llama-cpp" "llama.cpp standalone inference CLI + server" off \
"open-webui" "Open WebUI browser UI for Ollama / LLM backends" off \
"claude" "Claude Code Anthropic CLI via npm" off \
"networking-cli" "Networking CLI nmap · nethogs · mitmproxy · httpie" off \
"disk-recovery" "Disk Recovery ddrescue · f3" off \
"himalaya" "Himalaya terminal email client (AUR)" off \
"gnuplot" "Gnuplot scientific plotting" off \
"povray" "POV-Ray ray-tracing renderer" off \
"blender" "Blender 3D creation suite" off \
"toot" "toot Mastodon CLI client (AUR)" off \
"db-clients" "DB Clients pgcli · mycli" off \
"mysql" "MySQL / MariaDB mariadb server + setup" off \
"productivity" "Productivity taskwarrior · watson · jrnl" off \
"yt-dlp" "yt-dlp YouTube / media downloader" off \
"sox" "SoX audio processing toolkit" off \
"imagemagick" "ImageMagick image manipulation" off \
"ffmpeg" "FFmpeg extras thumbnailer · GStreamer codecs" off \
"localtunnel" "LocalTunnel expose localhost via tunnel" off \
"butter" "butter btrfs snapshot backup (AUR)" off \
"tlp" "TLP laptop power management" off \
"steam" "Steam gaming platform" off \
"vesktop" "Vesktop Discord + Vencord theme" off \
"spotify" "Spotify launcher + Spicetify theming" off \
"prism" "PrismLauncher Minecraft launcher (Flatpak)" off \
"vintagestory" "Vintage Story survival game (AUR)" off \
"localsend" "LocalSend LAN file transfer (AUR)" off \
"croc" "croc cross-platform file transfer" off \
"onlyoffice" "OnlyOffice office suite (AUR)" off \
"wireshark" "Wireshark network packet analyser (GUI)" off \
"k8s" "Kubernetes tools kubectl · podman-desktop" off \
"docker" "Docker docker · docker-compose" off \
"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 \
"zfs" "ZFS zfs-dkms kernel module" off \
"wprs" "WPRS wprs-git (AUR)" off \
\
"chromium" "Chromium open-source browser (official)" off \
"firefox-browser" "Firefox Mozilla browser (official)" off \
"zen-browser" "Zen Browser Firefox-based privacy browser (AUR)" off \
"nyxt" "Nyxt keyboard-driven browser (AUR)" off \
"librewolf" "LibreWolf hardened Firefox fork (AUR)" off \
"min-browser" "Min minimal Electron browser (AUR)" off \
\
"vscodium" "VSCodium telemetry-free VS Code (AUR)" off \
"zed-ide" "Zed high-performance Rust IDE (official)" off \
"geany" "Geany lightweight IDE + plugins (official)" off \
"codeblocks" "Code::Blocks C/C++ IDE (official)" off \
"kate" "Kate KDE advanced text editor (official)" off \
3>&1 1>&2 2>&3) || SELECTED_APPS=""
fi
# ── Confirmation (interactive mode only) ──────────────────────────────────────
if ! $ANSWERFILE_MODE; then
SUMMARY=""
[[ -n "$HOSTNAME_SET" ]] && SUMMARY+=" ✦ Hostname: $HOSTNAME_SET\n"
[[ "$COMPONENTS" == *"pkg"* ]] && SUMMARY+=" ✦ Package managers (yay, nvm, rust)\n"
[[ "$COMPONENTS" == *"core"* ]] && SUMMARY+=" ✦ Core packages\n"
[[ "$COMPONENTS" == *"svc"* ]] && SUMMARY+=" ✦ Core services\n"
[[ "$COMPONENTS" == *"shell"* ]] && SUMMARY+=" ✦ Shell setup\n"
[[ "$DE" != "none" ]] && SUMMARY+=" ✦ Desktop environment: $DE\n"
if [[ -n "$SELECTED_APPS" ]]; then
SUMMARY+="\n Applications:\n"
[[ "$SELECTED_APPS" == *"ollama"* ]] && SUMMARY+=" ✦ Ollama\n"
[[ "$SELECTED_APPS" == *"llama-cpp"* ]] && SUMMARY+=" ✦ llama.cpp\n"
[[ "$SELECTED_APPS" == *"open-webui"* ]] && SUMMARY+=" ✦ Open WebUI\n"
[[ "$SELECTED_APPS" == *"claude"* ]] && SUMMARY+=" ✦ Claude Code\n"
[[ "$SELECTED_APPS" == *"networking-cli"* ]] && SUMMARY+=" ✦ Networking CLI (nmap, nethogs, mitmproxy, httpie)\n"
[[ "$SELECTED_APPS" == *"disk-recovery"* ]] && SUMMARY+=" ✦ Disk Recovery (ddrescue, f3)\n"
[[ "$SELECTED_APPS" == *"himalaya"* ]] && SUMMARY+=" ✦ Himalaya\n"
[[ "$SELECTED_APPS" == *"gnuplot"* ]] && SUMMARY+=" ✦ Gnuplot\n"
[[ "$SELECTED_APPS" == *"povray"* ]] && SUMMARY+=" ✦ POV-Ray\n"
[[ "$SELECTED_APPS" == *"blender"* ]] && SUMMARY+=" ✦ Blender\n"
[[ "$SELECTED_APPS" == *"toot"* ]] && SUMMARY+=" ✦ toot\n"
[[ "$SELECTED_APPS" == *"db-clients"* ]] && SUMMARY+=" ✦ DB Clients (pgcli, mycli)\n"
[[ "$SELECTED_APPS" == *"mysql"* ]] && SUMMARY+=" ✦ MySQL / MariaDB\n"
[[ "$SELECTED_APPS" == *"productivity"* ]] && SUMMARY+=" ✦ Productivity (taskwarrior, watson, jrnl)\n"
[[ "$SELECTED_APPS" == *"yt-dlp"* ]] && SUMMARY+=" ✦ yt-dlp\n"
[[ "$SELECTED_APPS" == *"sox"* ]] && SUMMARY+=" ✦ SoX\n"
[[ "$SELECTED_APPS" == *"imagemagick"* ]] && SUMMARY+=" ✦ ImageMagick\n"
[[ "$SELECTED_APPS" == *"ffmpeg"* ]] && SUMMARY+=" ✦ FFmpeg extras\n"
[[ "$SELECTED_APPS" == *"localtunnel"* ]] && SUMMARY+=" ✦ LocalTunnel\n"
[[ "$SELECTED_APPS" == *"butter"* ]] && SUMMARY+=" ✦ butter (btrfs backup)\n"
[[ "$SELECTED_APPS" == *"tlp"* ]] && SUMMARY+=" ✦ TLP\n"
[[ "$SELECTED_APPS" == *"steam"* ]] && SUMMARY+=" ✦ Steam\n"
[[ "$SELECTED_APPS" == *"vesktop"* ]] && SUMMARY+=" ✦ Vesktop + Vencord theme\n"
[[ "$SELECTED_APPS" == *"spotify"* ]] && SUMMARY+=" ✦ Spotify + Spicetify\n"
[[ "$SELECTED_APPS" == *"prism"* ]] && SUMMARY+=" ✦ PrismLauncher\n"
[[ "$SELECTED_APPS" == *"vintagestory"* ]] && SUMMARY+=" ✦ Vintage Story\n"
[[ "$SELECTED_APPS" == *"localsend"* ]] && SUMMARY+=" ✦ LocalSend\n"
[[ "$SELECTED_APPS" == *"croc"* ]] && SUMMARY+=" ✦ croc\n"
[[ "$SELECTED_APPS" == *"onlyoffice"* ]] && SUMMARY+=" ✦ OnlyOffice\n"
[[ "$SELECTED_APPS" == *"wireshark"* ]] && SUMMARY+=" ✦ Wireshark\n"
[[ "$SELECTED_APPS" == *"k8s"* ]] && SUMMARY+=" ✦ Kubernetes tools\n"
[[ "$SELECTED_APPS" == *"docker"* ]] && SUMMARY+=" ✦ Docker + Compose\n"
[[ "$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"
[[ "$SELECTED_APPS" == *"zfs"* ]] && SUMMARY+=" ✦ ZFS\n"
[[ "$SELECTED_APPS" == *"wprs"* ]] && SUMMARY+=" ✦ WPRS\n"
[[ "$SELECTED_APPS" == *"chromium"* ]] && SUMMARY+=" ✦ Chromium\n"
[[ "$SELECTED_APPS" == *"firefox-browser"* ]] && SUMMARY+=" ✦ Firefox\n"
[[ "$SELECTED_APPS" == *"zen-browser"* ]] && SUMMARY+=" ✦ Zen Browser\n"
[[ "$SELECTED_APPS" == *"nyxt"* ]] && SUMMARY+=" ✦ Nyxt\n"
[[ "$SELECTED_APPS" == *"librewolf"* ]] && SUMMARY+=" ✦ LibreWolf\n"
[[ "$SELECTED_APPS" == *"min-browser"* ]] && SUMMARY+=" ✦ Min Browser\n"
[[ "$SELECTED_APPS" == *"vscodium"* ]] && SUMMARY+=" ✦ VSCodium\n"
[[ "$SELECTED_APPS" == *"zed-ide"* ]] && SUMMARY+=" ✦ Zed IDE\n"
[[ "$SELECTED_APPS" == *"geany"* ]] && SUMMARY+=" ✦ Geany\n"
[[ "$SELECTED_APPS" == *"codeblocks"* ]] && SUMMARY+=" ✦ Code::Blocks\n"
[[ "$SELECTED_APPS" == *"kate"* ]] && SUMMARY+=" ✦ Kate\n"
fi
dialog --backtitle "$BACKTITLE" \
--title " Confirm Installation " \
--yesno "\n Components to install:\n\n${SUMMARY}\n Log: $LOG\n\n Proceed?" \
24 62 || { clear; echo "Aborted."; exit 0; }
fi
count_steps "$COMPONENTS" "$DE" "$SELECTED_APPS"
@ -410,10 +504,85 @@ fi
[[ "$SELECTED_APPS" == *"codeblocks"* ]] && run_module "Code::Blocks" "$APPS/codeblocks.sh"
[[ "$SELECTED_APPS" == *"kate"* ]] && run_module "Kate" "$APPS/kate.sh"
# ── Done ──────────────────────────────────────────────────────────────────────
dialog --backtitle "$BACKTITLE" \
--title " Done " \
--msgbox "\n All selected components installed.\n\n Log: $LOG\n\n A reboot may be required for all changes to take effect.\n" 12 58
# ── Colorway (final step) ─────────────────────────────────────────────────────
# Read defaults from repo colors.conf for pre-population
declare -A _cdef
if [[ -f "$DOTFILES_DIR/colors.conf" ]]; then
while IFS='=' read -r k v; do
k="${k%%[[:space:]]*}"
[[ "$k" =~ ^[[:space:]]*# || -z "$k" ]] && continue
v="${v%%#*}"; v="${v//[[:space:]]/}"; v="${v^^}"
_cdef[$k]="$v"
done < "$DOTFILES_DIR/colors.conf"
fi
DEF_TEXT="${_cdef[COLOR_TEXT]:-D6ABAB}"
DEF_BG="${_cdef[COLOR_BG]:-1A1A1A}"
DEF_HIGHLIGHT="${_cdef[COLOR_HIGHLIGHT]:-E40046}"
DEF_DARK="${_cdef[COLOR_DARK]:-5018DD}"
DEF_RED="${_cdef[COLOR_RED]:-F50505}"
clear
printf "\n Done. Log: %s\n\n" "$LOG"
_write_colors_conf() {
local out="$1" t="$2" b="$3" h="$4" d="$5" r="$6"
printf 'COLOR_TEXT=%s\nCOLOR_BG=%s\nCOLOR_HIGHLIGHT=%s\nCOLOR_DARK=%s\nCOLOR_RED=%s\n' \
"${t^^}" "${b^^}" "${h^^}" "${d^^}" "${r^^}" > "$out"
}
if $ANSWERFILE_MODE; then
# Apply colors from answerfile if any are set
if [[ -n "$AF_COLOR_TEXT$AF_COLOR_BG$AF_COLOR_HIGHLIGHT$AF_COLOR_DARK$AF_COLOR_RED" ]]; then
TMP_COLORS="$TMP_D/colors.conf"
_write_colors_conf "$TMP_COLORS" \
"${AF_COLOR_TEXT:-$DEF_TEXT}" \
"${AF_COLOR_BG:-$DEF_BG}" \
"${AF_COLOR_HIGHLIGHT:-$DEF_HIGHLIGHT}" \
"${AF_COLOR_DARK:-$DEF_DARK}" \
"${AF_COLOR_RED:-$DEF_RED}"
printf "Applying colorway from answerfile...\n" | tee -a "$LOG"
bash "$DOTFILES_DIR/apply-theme.sh" "$TMP_COLORS" 2>&1 | tee -a "$LOG" || true
fi
else
# Interactive: show color form dialog
COLORWAY_RAW=$(dialog --backtitle "$BACKTITLE" \
--title " Colorway (optional) " \
--form "\n Customize theme colors — bare 6-digit hex, no #.\n Leave unchanged to skip colorway setup.\n" \
16 62 5 \
"COLOR_TEXT " 1 1 "$DEF_TEXT" 1 18 10 7 \
"COLOR_BG " 2 1 "$DEF_BG" 2 18 10 7 \
"COLOR_HIGHLIGHT " 3 1 "$DEF_HIGHLIGHT" 3 18 10 7 \
"COLOR_DARK " 4 1 "$DEF_DARK" 4 18 10 7 \
"COLOR_RED " 5 1 "$DEF_RED" 5 18 10 7 \
3>&1 1>&2 2>&3) || COLORWAY_RAW=""
if [[ -n "$COLORWAY_RAW" ]]; then
mapfile -t _cv <<< "$COLORWAY_RAW"
N_TEXT="${_cv[0]:-$DEF_TEXT}"
N_BG="${_cv[1]:-$DEF_BG}"
N_HIGHLIGHT="${_cv[2]:-$DEF_HIGHLIGHT}"
N_DARK="${_cv[3]:-$DEF_DARK}"
N_RED="${_cv[4]:-$DEF_RED}"
if [[ "${N_TEXT^^}" != "$DEF_TEXT" || \
"${N_BG^^}" != "$DEF_BG" || \
"${N_HIGHLIGHT^^}" != "$DEF_HIGHLIGHT" || \
"${N_DARK^^}" != "$DEF_DARK" || \
"${N_RED^^}" != "$DEF_RED" ]]; then
TMP_COLORS="$TMP_D/colors.conf"
_write_colors_conf "$TMP_COLORS" "$N_TEXT" "$N_BG" "$N_HIGHLIGHT" "$N_DARK" "$N_RED"
clear
printf "\n\033[1;35m Applying colorway...\033[0m\n\n"
bash "$DOTFILES_DIR/apply-theme.sh" "$TMP_COLORS" 2>&1 | tee -a "$LOG" || true
fi
fi
fi
# ── Done ──────────────────────────────────────────────────────────────────────
if $ANSWERFILE_MODE; then
printf "\nDone. Log: %s\n" "$LOG"
else
dialog --backtitle "$BACKTITLE" \
--title " Done " \
--msgbox "\n All selected components installed.\n\n Log: $LOG\n\n A reboot may be required for all changes to take effect.\n" 12 58
clear
printf "\n Done. Log: %s\n\n" "$LOG"
fi