chore: add inline comments across all modules and configs

main
Amir Alexander Abdelbaki 2026-06-25 13:07:06 +02:00
parent bc564f9b83
commit cb464c830f
133 changed files with 5369 additions and 625 deletions

92
.bashrc
View File

@ -1,28 +1,57 @@
#
# ~/.bashrc
# ~/.bashrc — Interactive bash shell configuration
#
# This file is sourced for every non-login interactive bash session.
# Bash is the fallback shell on this system; zsh is the primary interactive
# shell. This file mirrors a core subset of .zshrc's aliases so the
# environment is consistent inside TTYs and distrobox containers.
#
# If not running interactively, don't do anything
# Guard: only run for interactive shells.
# $- contains the set of active shell option flags; 'i' means interactive.
# If 'i' is absent, return immediately — this prevents errors when bash is
# invoked non-interactively (e.g., from scripts or scp file transfers).
[[ $- != *i* ]] && return
# ─── Core aliases ──────────────────────────────────────────────────────────────
# Colorize ls and grep output — file types and match highlights stand out visually.
alias ls='ls --color=auto'
alias grep='grep --color=auto'
# $PS1: minimal prompt showing [user@host workingdir]$
# This is the bash default; starship replaces it at the bottom of this file.
# Kept here as a safe fallback if starship fails to initialize.
PS1='[\u@\h \W]\$ '
# Detailed directory listing with ISO timestamps (YYYY-MM-DD HH:MM).
# ISO format is sortable and unambiguous compared to locale-default date strings.
alias ll="ls -la --time-style=long-iso"
alias l="ll"
alias l="ll" # Short form for everyday use
# Quick parent-directory navigation — equivalent to 'cd ..'
alias ..="cd .."
# Editor shortcuts: micro is a terminal text editor with intuitive keybindings
# (Ctrl+S to save, Ctrl+Q to quit), suitable for quick in-terminal edits.
alias m="micro"
alias sm="sudo micro"
alias sm="sudo micro" # Elevated edit for system files owned by root
alias gita="git add ."
alias gitc="git commit -m"
alias gitp="git push"
# ─── Git shortcuts ─────────────────────────────────────────────────────────────
alias gita="git add ." # Stage all changes in the current working tree
alias gitc="git commit -m" # Commit with an inline message, e.g.: gitc "fix: typo"
alias gitp="git push" # Push current branch to its remote tracking branch
# curl-based weather lookup — wttr.in returns ANSI-art weather for the terminal.
# The trailing slash with no city causes wttr.in to auto-detect location via IP.
alias weather="curl https://wttr.in/"
# ─── gitf(): stage → commit → push in a single command ───────────────────────
# Usage: gitf "commit message"
# Collapses the three-step add/commit/push workflow for quick, minor commits.
# The $1 guard prevents an empty commit message (which git would reject anyway,
# but this gives a cleaner user-facing error).
function gitf() {
if [ -z $1 ]; then
@ -35,31 +64,66 @@ function gitf() {
fi
}
# ─── Kitty terminal integration ────────────────────────────────────────────────
# icat: display images inline in the terminal using Kitty's icat kitten.
# Only works inside a Kitty terminal; other terminals will display an error.
alias icat="kitten icat"
# Two ways to clear the screen — cls is familiar muscle memory from Windows cmd.
alias cls="clear"
# Kitten SSH: wraps ssh with Kitty's ssh kitten so the remote shell inherits
# correct terminfo entries and Kitty protocol extensions (OSC 52 clipboard,
# proper backspace, 24-bit color, etc.) without manual TERM setup on the server.
alias ssh="kitten ssh"
alias ssk="kitten ssh"
alias ssk="kitten ssh" # Typo-resilient duplicate
# ─── Hardware / misc ───────────────────────────────────────────────────────────
# tio: terminal I/O program for serial port communication (embedded dev, etc.).
# '-a latest' attaches to the most recently enumerated serial device.
# sudo is required because /dev/ttyUSB* and /dev/ttyACM* are typically owned
# by the 'uucp' or 'dialout' group and not accessible as a regular user by default.
alias serial="sudo tio -a latest"
# t: alias for 'wd' (warp directory) — jump to named directory bookmarks.
# Bookmarks are set with: wd add <name> and jumped to with: t <name>
alias t="wd"
# Filtered listing — pipe a long-format listing through grep for fast file search.
alias lgrep="l | grep"
alias lg="lgrep"
alias lg="lgrep" # Short form
# ─── y(): yazi file manager with shell directory-change on exit ─────────────────
# Yazi is a terminal file manager. Without this wrapper, any directory you
# navigate to inside yazi is lost when it exits (it ran in a subshell).
# This wrapper passes --cwd-file to yazi; yazi writes its final CWD there,
# and the wrapper reads it to cd the parent shell into that directory.
function y() {
local tmp="$(mktemp -t "yazi-cwd.XXXXXX")" cwd
yazi "$@" --cwd-file="$tmp"
local tmp="$(mktemp -t "yazi-cwd.XXXXXX")" cwd # temp file to capture yazi's last dir
yazi "$@" --cwd-file="$tmp" # run yazi; it writes final dir to $tmp
if cwd="$(command cat -- "$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then
builtin cd -- "$cwd"
builtin cd -- "$cwd" # apply the directory change to this shell
fi
rm -f -- "$tmp"
rm -f -- "$tmp" # clean up the temp file
}
# ─── Prompt & startup ──────────────────────────────────────────────────────────
# Initialize starship for bash. Starship is a cross-shell prompt engine configured
# in ~/Dotfiles/starship.toml. It replaces the plain PS1 above with a rich,
# segment-based prompt showing user, path, git branch/status, language versions,
# and the current time — all styled with the CyberQueer color palette.
eval "$(starship init bash)"
fastfetch --logo-color-1 red --logo-color-2 red --color red -l ~/Dotfiles/pin.txt
# Display system info on shell start using fastfetch with the custom ASCII logo.
# Colors are forced to red to match the CyberQueer theme branding.
fastfetch --logo-color-1 red --logo-color-2 red --color red -l ~/Dotfiles/pin.txt
# Load the Rust/Cargo environment — primarily adds ~/.cargo/bin to PATH so that
# binaries installed via `cargo install` (e.g., ripgrep, bat, starship itself)
# are immediately accessible in this session.
. "$HOME/.cargo/env"

17
.vimrc
View File

@ -1,2 +1,19 @@
" ~/.vimrc — Vim configuration
"
" This is a minimal vimrc used for legacy Vim (when nvim is not available).
" The primary editor on this system is Neovim; Vim is only kept as a fallback.
" The config delegates all theme setup to a shared theme file so that both
" Vim and Neovim pick up the same CyberQueer color scheme.
"
" Load the shared theme settings file from the dotfiles repo.
" This file sets options like 'set termguicolors', 'set background=dark', etc.
" Sourcing it here keeps the Vim and Neovim theme configurations in sync
" without duplicating settings in two places.
source ~/Dotfiles/vim/theme
" Apply the CyberQueer color scheme.
" cyberqueer.nvim is stored in the nvim color directory but is also compatible
" with Vim when placed in ~/.vim/colors/ or when Vim's runtimepath includes
" the nvim config directory.
colorscheme cyberqueer.nvim

231
.zshrc
View File

@ -1,3 +1,23 @@
# ~/.zshrc — Primary interactive Zsh shell configuration
#
# This is the main shell config for all interactive zsh sessions on this system.
# It wraps Oh My Zsh (for plugin management and completions), then loads a wide
# set of aliases, utility functions, and environment variables.
#
# Structure:
# 1. GNU grep detection guard (activates OMZ block only on GNU/Linux)
# 2. Oh My Zsh setup and plugin list
# 3. Editor and terminal environment variables
# 4. Aliases for navigation, editors, git, distrobox, python, etc.
# 5. Shell functions: gitf, dbc, dbt, y, n, calc
# 6. Starship prompt init
# 7. PATH additions (spicetify, nvm, pipx)
#
# ─── GNU grep guard ────────────────────────────────────────────────────────────
# Detects whether the system grep is GNU grep (Linux) by checking its --help output.
# If it is GNU grep, the entire Oh My Zsh block runs. On non-GNU systems (macOS BSD,
# for example) this block would be skipped, preventing GNU-only flag errors.
GREPOUTPUT=$( grep --help 2>&1 )
RESULT=$( echo $GREPOUTPUT | grep gnu )
if [ "${RESULT}" != "" ]; then
@ -5,6 +25,9 @@ if [ "${RESULT}" != "" ]; then
# If you come from bash you might have to change your $PATH.
# export PATH=$HOME/bin:$HOME/.local/bin:/usr/local/bin:$PATH
# ─── Oh My Zsh ─────────────────────────────────────────────────────────────────
# OMZ is a community framework for managing zsh configuration and plugins.
# It provides plugin loading, theme support, and helper functions.
# Path to your Oh My Zsh installation.
export ZSH="$HOME/.oh-my-zsh"
@ -12,8 +35,15 @@ export ZSH="$HOME/.oh-my-zsh"
# load a random theme each time Oh My Zsh is loaded, in which case,
# to know which specific one was loaded, run: echo $RANDOM_THEME
# See https://github.com/ohmyzsh/ohmyzsh/wiki/Themes
# robbyrussell is the classic OMZ default; starship overrides it at the bottom.
ZSH_THEME="robbyrussell"
# WALK_MAIN_COLOR: accent color for the 'walk' interactive directory browser.
# Matches the CyberQueer electric-violet secondary accent color.
export WALK_MAIN_COLOR="#5018DD"
# lk(): navigate to a directory selected interactively via 'walk' (a fuzzy dir browser).
# walk prints the selected path to stdout; this function cd's into it.
function lk {
cd "$(walk "$@")"
}
@ -73,30 +103,35 @@ function lk {
# Would you like to use another custom folder than $ZSH/custom?
# ZSH_CUSTOM=/path/to/new-custom-folder
# ─── OMZ Plugin list ───────────────────────────────────────────────────────────
# Plugins add completions, keybindings, and helper functions.
# They are loaded from $ZSH/plugins/ (built-in) or $ZSH_CUSTOM/plugins/ (installed).
# Too many plugins slow shell startup — only keep what's actively used.
# Which plugins would you like to load?
# Standard plugins can be found in $ZSH/plugins/
# Custom plugins may be added to $ZSH_CUSTOM/plugins/
# Example format: plugins=(rails git textmate ruby lighthouse)
# Add wisely, as too many plugins slow down shell startup.
plugins=( git
zsh-syntax-highlighting
zsh-autosuggestions
wd
archlinux
git
git-auto-fetch
nmap
perms
zsh-interactive-cd
zsh-navigation-tools
z
ufw
web-search
timer
sudo
taskwarrior
plugins=( git # git aliases (gst, gco, ga, gp, etc.)
zsh-syntax-highlighting # colorizes valid commands green, errors red (installed via zshplugins.sh)
zsh-autosuggestions # ghost-text completions based on command history (installed via zshplugins.sh)
wd # warp directory: bookmark dirs with 'wd add <name>', jump with 'wd <name>'
archlinux # pacman/yay shortcuts (pacin, pacrem, etc.)
git # duplicate entry — harmless but redundant
git-auto-fetch # automatically runs 'git fetch' in the background in git repos
nmap # completions for nmap
perms # helper functions for setting file permissions
zsh-interactive-cd # interactive fuzzy cd using fzf
zsh-navigation-tools # panel-style history/dir navigation widgets
z # jump to frecent directories: 'z projectname' jumps to most visited match
ufw # completions for ufw (Uncomplicated Firewall)
web-search # open web searches from the terminal: 'google foo'
timer # shows command execution time in the prompt for long-running commands
sudo # press Esc twice to prepend 'sudo' to the previous command
taskwarrior # completions for the taskwarrior task manager
)
# Load Oh My Zsh — sources all plugins, the theme, and OMZ helper functions.
source $ZSH/oh-my-zsh.sh
# User configuration
@ -127,40 +162,73 @@ source $ZSH/oh-my-zsh.sh
# Example aliases
# alias zshconfig="mate ~/.zshrc"
# alias ohmyzsh="mate ~/.oh-my-zsh"
fi
fi # end of the GNU grep guard block
# ─── Default editor ────────────────────────────────────────────────────────────
# nvim is the default editor for git commits, sudoedit, cron, etc.
# This is set outside the guard block so it always applies regardless of grep detection.
export EDITOR='nvim'
# ─── Startup system info ───────────────────────────────────────────────────────
# Show fastfetch system info on every new interactive shell, except inside
# distrobox containers. CONTAINER_ID is set by distrobox on entry; checking
# for it prevents a duplicate/confusing fetch banner inside containers.
if [ -z "${CONTAINER_ID}" ]; then
fastfetch --logo-color-1 red --logo-color-2 red --color red -l ~/Dotfiles/resources/pin.txt
fastfetch --logo-color-1 red --logo-color-2 red --color red -l ~/Dotfiles/resources/pin.txt
fi
# Force 256-color + true-color mode. xterm-256color is widely recognized by
# remote servers and terminal multiplexers; it ensures color-capable apps
# (vim, tmux, etc.) enable their full color support even if COLORTERM isn't set.
export TERM=xterm-256color
# ─── Core aliases ──────────────────────────────────────────────────────────────
# Colorize ls and grep output for readability.
alias ls='ls --color=auto'
alias grep='grep --color=auto'
# Detailed listing with ISO timestamps — unambiguous YYYY-MM-DD HH:MM format.
alias ll="ls -la --time-style=long-iso"
alias l="ll"
alias l="ll" # Short form
# lsblk with extra columns: filesystem type, drive model, and UUID.
# Useful for identifying disks, partitions, and their UUIDs for /etc/fstab.
alias ldsk="lsblk -o+FSTYPE,MODEL,UUID"
# Quick parent-directory jump.
alias ..="cd .."
alias m="micro"
alias sm="sudo micro"
alias e="edit-in-kitty"
alias et="edit-in-kitty --type=tab"
alias ek="edit-in-kitty --type=window"
alias ew="kitty --detach micro"
# ─── Editor aliases ────────────────────────────────────────────────────────────
alias m="micro" # micro: simple terminal editor, good for quick config edits
alias sm="sudo micro" # sudo micro: edit system-owned files
# edit-in-kitty: open a file in micro inside a Kitty terminal panel/tab.
# These variants control where the new editor window appears.
alias e="edit-in-kitty" # default placement (new OS window)
alias et="edit-in-kitty --type=tab" # open in a new Kitty tab
alias ek="edit-in-kitty --type=window" # open in a new Kitty split window
alias ew="kitty --detach micro" # detach: open in a completely new Kitty window, non-blocking
# ex: open the current directory in Thunar (GTK file manager).
# Useful for quick GUI access without leaving the terminal.
alias ex="thunar ."
# fast-ssh: a wrapper around ssh with fuzzy host selection from ~/.ssh/config.
alias fs="fast-ssh"
alias gita="git add ."
alias gitc="git commit -m"
alias gitp="git push"
alias gitg="git pull"
alias gitfuck="git commit --amend -m"
# ─── Git aliases ───────────────────────────────────────────────────────────────
alias gita="git add ." # Stage all changes
alias gitc="git commit -m" # Commit with message
alias gitp="git push" # Push to remote
alias gitg="git pull" # Pull from remote (g for 'get')
alias gitfuck="git commit --amend -m" # Fix the last commit message without creating a new commit
# gitf(): one-shot add + commit + push. Guards against empty commit messages.
function gitf() {
if [ -z $1 ]; then
@ -172,80 +240,145 @@ function gitf() {
git push
fi
}
alias icat="kitty +kitten icat"
alias cls="clear"
alias c="cls"
# icat: display images inline in Kitty using the Kitty graphics protocol.
# Note: in zsh the kitten is invoked via 'kitty +kitten' rather than the
# standalone 'kitten' binary used in .bashrc.
alias icat="kitty +kitten icat"
alias cls="clear" # Windows-style clear alias
alias c="cls" # Ultra-short clear
# kitten ssh: wraps ssh with Kitty's ssh kitten for proper terminfo propagation.
alias ks="kitten ssh"
#function s() {
# alacritty -e sh -c "ssh $1" &
#}
# (Legacy: used to open ssh in an alacritty window; replaced by Kitty-native approach)
# tio: serial port terminal. '-a latest' picks the most-recently plugged device.
alias serial="sudo tio -a latest"
# wd: warp directory bookmarks. 't' as an alias makes it one keystroke.
alias t="wd"
# Terminal weather report via wttr.in (ANSI art, no browser needed).
alias weather="curl https://wttr.in/"
# Pipe detailed listing through grep for quick in-directory file search.
alias lgrep="l | grep"
alias lg="lgrep"
# treegrep / tg: list the entire directory tree recursively and grep in the output.
# Useful for finding files by partial name across deep hierarchies.
# tree -fi: full-path (-f), no indentation graph (-i) — clean output for grepping.
alias treegrep="tree -fi | grep"
alias tg="treegrep"
# cpwd: copy the current working directory path to the Wayland clipboard.
# wl-copy is the Wayland equivalent of xclip/xsel.
alias cpwd="pwd | wl-copy"
# v/vi: use neovim as the primary $VISUAL editor instead of legacy vim.
alias v="nvim"
alias sv="sudoedit"
alias dbe="distrobox enter"
alias dbrm="distrobox rm"
alias dbs="distrobox stop"
alias fig="find | grep"
alias mfetch="fastfetch --kitty-icat ~/Pictures/profile.jpg --logo-width 30 --logo-height 30 --color red"
alias vi="nvim"
alias pyr="python"
alias pynowr='python -W "ignore"'
# sv: sudoedit — opens a file as root in $SUDO_EDITOR (nvim) using a safe
# temp-file mechanism, avoiding the security risk of running nvim as root directly.
alias sv="sudoedit"
# ─── Distrobox aliases ─────────────────────────────────────────────────────────
# Distrobox runs OCI containers that share the host's home directory, so tools
# from other distros can be used transparently alongside Arch packages.
alias dbe="distrobox enter" # Enter (attach to shell of) a running container
alias dbrm="distrobox rm" # Remove a stopped container
alias dbs="distrobox stop" # Stop a running container
# fig: find files anywhere in the tree and filter by name using grep.
# Equivalent to `find | grep`, but faster to type.
alias fig="find | grep"
# mfetch: fastfetch with a profile photo displayed via the Kitty image protocol.
# --logo-width/height control the inline image dimensions in terminal cells.
alias mfetch="fastfetch --kitty-icat ~/Pictures/profile.jpg --logo-width 30 --logo-height 30 --color red"
# Python shortcuts
alias pyr="python" # Shorter form for Python 3 interpreter
alias pynowr='python -W "ignore"' # Run Python suppressing all warnings (useful for noisy scripts)
# ─── dbc(): create a distrobox container ──────────────────────────────────────
# Usage: dbc <image> <name>
# e.g.: dbc ubuntu mybox → pulls ubuntu:latest and names it 'mybox'
function dbc()
{
distrobox create --image "${1}:latest" --name $2
}
# ─── dbt(): create and immediately enter a distrobox container ────────────────
# Usage: dbt <image>
# The container name matches the image name for simplicity.
function dbt()
{
distrobox create --image "${1}:latest" --name $1
distrobox enter $1
}
# ─── y(): yazi file manager with persistent directory change ─────────────────
# Without this wrapper, directory navigation in yazi is lost when it exits
# because yazi runs in a subshell. The --cwd-file mechanism lets yazi report
# its final directory; this function reads it and cd's the parent shell there.
function y() {
local tmp="$(mktemp -t "yazi-cwd.XXXXXX")" cwd
yazi "$@" --cwd-file="$tmp"
local tmp="$(mktemp -t "yazi-cwd.XXXXXX")" cwd # temp file for yazi's final cwd
yazi "$@" --cwd-file="$tmp" # run yazi with cwd reporting enabled
if cwd="$(command cat -- "$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then
builtin cd -- "$cwd"
builtin cd -- "$cwd" # apply the directory change to the parent shell
fi
rm -f -- "$tmp"
rm -f -- "$tmp" # clean up temp file
}
# ─── n(): cd + show directory listing ─────────────────────────────────────────
# A quick navigation helper: cd into $1, print the absolute path, and list contents.
# Useful when you want a visual confirmation of where you landed.
function n() {
cd $1
echo$(pwd)
echo $(pwd)
ls -la
}
# ─── calc(): quick arithmetic evaluator ───────────────────────────────────────
# Passes its arguments to bc with the -l flag (math library for floating point).
# Usage: calc "2.5 * 3 + 1" → prints 8.5
# printf '%s\n' "$@" handles spaces and multiple expressions correctly.
calc() { printf "%s\n" "$@" | bc -l; }
# ─── Prompt ────────────────────────────────────────────────────────────────────
# Initialize starship for zsh. Starship reads ~/Dotfiles/starship.toml and
# generates a rich prompt segment showing user, path, git state, tool versions,
# and the current time — styled with the CyberQueer red/pink/violet palette.
eval "$(starship init zsh)"
# ─── PATH additions ────────────────────────────────────────────────────────────
# Spicetify: Spotify theme/extension manager. Its CLI lives in ~/.spicetify.
export PATH=$PATH:$HOME/.spicetify
# NVM (Node Version Manager): manages multiple Node.js versions.
# The conditional [ -s ... ] checks that the file exists and is non-empty before sourcing,
# so missing nvm installations don't error out silently.
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
# SUDO_EDITOR: the editor invoked by sudoedit / sudo -e.
# Using nvim here instead of vi/nano keeps the editing experience consistent.
export SUDO_EDITOR=/usr/bin/nvim
# Created by `pipx` on 2025-05-12 08:58:28
# pipx installs Python CLI tools in isolated environments under ~/.local/bin.
# The two lines handle both the old tilde-literal form and the expanded $HOME form;
# the second one takes precedence in PATH (higher priority).
export PATH="$PATH:~/.local/bin"
export PATH="$HOME/.local/bin:$PATH"

View File

@ -110,12 +110,15 @@
# A TCP port number the daemon will listen on.
# Default: disabled
# Exposing a TCP socket lets local tools like clamdscan and virus scanners
# contact clamd without needing a shared Unix socket path.
TCPSocket 3310
# By default clamd binds to INADDR_ANY.
# This option allows you to restrict the TCP address and provide
# some degree of protection from the outside world.
# Default: disabled
# Binding only to localhost prevents external hosts from submitting files to clamd.
TCPAddr localhost
# Maximum length the queue of pending connections may grow to.
@ -762,21 +765,30 @@ TCPAddr localhost
# Deprecated option to alert on encrypted archives and documents (encrypted .zip, .7zip, .rar, .pdf).
# Default: no
#ArchiveBlockEncrypted no
# Log time with each message.
# ── Active (non-default) settings ──────────────────────────────────────────────
# Timestamp every log entry so events can be correlated with system logs.
# Default: no
LogTime yes
# Log additional information about the infected file, such as its
# size and hash, together with the virus name.
# Include file size and MD5/SHA hashes in threat reports — useful for
# submitting false-positive or false-negative reports to ClamAV.
ExtendedDetectionInfo yes
# Run as another user (clamd must be started by root for this option to work)
# Drop privileges to the 'clamav' system user after startup.
# clamd must be started as root for this directive to take effect.
# Default: don't drop privileges
User clamav
# Exclude the UID of the scanner itself from checking, to prevent loops
# Exclude file-access events triggered by the clamav user itself from
# on-access scanning — prevents the scanner from re-scanning its own reads
# and entering an infinite loop.
OnAccessExcludeUname clamav
# Watch the entire filesystem for on-access events via fanotify.
# Note: OnAccessPrevention cannot block events at the mount-point level,
# only when using OnAccessIncludePath (see comment below).
# Set the mount point where to recursively perform the scan,
# this could be every path or multiple path (one line for path)
OnAccessMountPath /
@ -788,41 +800,59 @@ OnAccessMountPath /
# It works with OnAccessIncludePath, as long as /usr and /etc are not included.
# Including /var while activating prevention is also not recommended, because
# this would slow down package installation by a factor of 1000.
#
# Prevention is disabled here because OnAccessMountPath / is used;
# enabling it with a full-filesystem mount path causes severe slowdowns.
OnAccessPrevention no
# Also trigger scans on inotify events (create/move/rename) in addition to
# open events — catches malware dropped by scripts without being read first.
# Perform scans on newly created, moved, or renamed files
OnAccessExtraScanning yes
# Optionallyexclude root-owned processes
# OnAccessExcludeRootUID true
# Raise the default recursion cap (15) to handle deeply nested archives or
# directory trees; set high deliberately for comprehensive scanning.
# Maximum depth directories are scanned at.
# Default: 15
MaxDirectoryRecursion 200
# TCP socket commented out here — the active TCPSocket/TCPAddr declarations
# appear earlier in this file (generated section).
#TCPAddr localhost
#TCPSocket 3310
DetectPUA yes
HeuristicAlerts yes
ScanPE yes
ScanELF yes
ScanOLE2 yes
ScanPDF yes
ScanSWF yes
ScanXMLDOCS yes
ScanHWP3 yes
ScanOneNote yes
ScanMail yes
ScanHTML yes
ScanArchive yes
Bytecode yes
AlertBrokenExecutables yes
AlertBrokenMedia yes
AlertEncrypted yes
AlertEncryptedArchive yes
AlertEncryptedDoc yes
AlertOLE2Macros yes
AlertPartitionIntersection yes
# ── Enable all major scan categories ───────────────────────────────────────────
# Each directive below enables a specific scanner module. All default to "yes"
# in recent ClamAV, but are listed explicitly to document intent and prevent
# a future default change from silently disabling a scanner.
DetectPUA yes # Detect Potentially Unwanted Applications (adware, tools)
HeuristicAlerts yes # Enable heuristic (behavior-based) detection algorithms
ScanPE yes # Scan Windows PE executables (required for UPX/FSG decompression)
ScanELF yes # Scan Linux ELF executables
ScanOLE2 yes # Scan Microsoft Office documents and MSI files
ScanPDF yes # Decode and scan PDF files
ScanSWF yes # Scan Adobe Flash (SWF) files
ScanXMLDOCS yes # Scan XML-based document formats (docx, xlsx, etc.)
ScanHWP3 yes # Scan Korean HWP3 word-processor files
ScanOneNote yes # Scan Microsoft OneNote files
ScanMail yes # Parse and scan email messages (RFC 822/MIME)
ScanHTML yes # Normalize and scan HTML/JavaScript
ScanArchive yes # Unpack and scan inside ZIP, RAR, TAR, etc.
Bytecode yes # Execute ClamAV bytecode signatures (required for many modern detections)
AlertBrokenExecutables yes # Flag malformed PE/ELF files (often indicate deliberate obfuscation)
AlertBrokenMedia yes # Flag malformed images used to exploit media parsers
AlertEncrypted yes # Alert on any encrypted container
AlertEncryptedArchive yes # Alert specifically on encrypted ZIP/7z/RAR
AlertEncryptedDoc yes # Alert specifically on password-protected PDFs
AlertOLE2Macros yes # Alert on Office files containing VBA macros
AlertPartitionIntersection yes # Flag raw disk images with overlapping partitions
# Script to execute when a virus is detected.
# See /etc/clamav/virus-event.bash for the notification/logging logic.
# The script receives CLAM_VIRUSEVENT_FILENAME and CLAM_VIRUSEVENT_VIRUSNAME
# as environment variables (NOT via %f/%v — those were disabled for security).
VirusEvent /etc/clamav/virus-event.bash

View File

@ -1,14 +1,49 @@
#!/bin/bash
# One-shot installer for ClamAV with on-access scanning (clamonacc).
# Run as a regular user — individual commands use sudo where root is required.
# Requires: clamav package, and files in this directory (./clamav-sudoer etc.).
# Install the clamav package (provides clamd, clamonacc, freshclam, clamdscan).
sudo pacman -S clamav
# Deploy the sudoers drop-in that allows the clamav user to run freshclam
# without a password — needed for automated signature updates.
# -fr: force-overwrite + recursive (safe for single files too).
sudo cp -fr ./clamav-sudoer /etc/sudoers.d/clamav
# Deploy the custom daemon config (see clamd.conf in this directory for details
# on on-access mount path, scan settings, and VirusEvent hook).
sudo cp -fr ./clamd.conf /etc/clamav/clamd.conf
# Deploy the virus-event script that clamd calls when a threat is detected;
# typically sends a desktop notification or logs the event.
sudo cp -fr ./virus-event.bash /etc/clamav/virus-event.bash
# Deploy the custom systemd service unit for clamonacc (the on-access daemon).
# Placed in /usr/lib/systemd/system/ so it survives package upgrades without
# manual intervention (unit files in /etc/systemd/ take precedence but are
# overwritten by the package on reinstall).
sudo cp -fr ./clamav-clamonacc.service /usr/lib/systemd/system/clamav-clamonacc.service
#aa-complain clamd
# aa-complain clamd
# (AppArmor complain-mode left commented out — uncomment if AppArmor is active
# and clamonacc is blocked; complain mode logs denials without enforcing them.)
# Enable all four related units at boot:
# clamav-clamonacc : on-access real-time scanner (requires clamd to be up first)
# clamav-daemon : the clamd background scan service
# clamav-freshclam : daily signature update service
# clamav-freshclam-once.timer : one-shot timer that fires freshclam at boot
sudo systemctl enable clamav-clamonacc.service
sudo systemctl enable clamav-daemon.service
sudo systemctl enable clamav-freshclam.service
sudo systemctl enable clamav-freshclam-once.timer
sudo systemctl enable clamav-daemon.service
sudo systemctl enable clamav-freshclam.service
sudo systemctl enable clamav-freshclam-once.timer
# Perform an initial signature database download before the first boot into clamd.
# Without this, clamd will refuse to start because /var/lib/clamav is empty.
freshclam
# A reboot is required for on-access scanning to take full effect — the fanotify
# kernel API used by clamonacc needs a clean mount namespace with the watcher
# registered from the start.
reboot

View File

@ -1,9 +1,25 @@
# CyberQueer Theme Colors
# colors.conf — CyberQueer Theme Color Palette
#
# Central definition of the five named colors that make up the CyberQueer theme.
# All other config files (hyprland, kitty, waybar, yazi, starship, etc.) reference
# these hex values. When you change a color here, run apply-theme.sh to propagate
# the change to every deployed config file at once.
#
# Format rules (enforced by apply-theme.sh's parse_colors function):
# - Bare 6-digit hex values only — NO # prefix, NO quotes
# - Inline comments after the value are allowed (stripped during parsing)
# - Keys must be exact: COLOR_TEXT, COLOR_BG, COLOR_HIGHLIGHT, COLOR_DARK, COLOR_RED
#
# Usage:
# Edit values below, then run: ~/Dotfiles/apply-theme.sh
# The script compares these values against the last-applied state
# (~/.config/colors.state) and only replaces changed colors.
#
# Edit values, then run: ~/Dotfiles/apply-theme.sh
# Bare 6-digit hex values only (no # prefix)
COLOR_TEXT=D6ABAB # Quasi-White Text — foreground, labels
COLOR_BG=1A1A1A # Gray Background — base surface
COLOR_HIGHLIGHT=E40046 # Hot Pink Accent — primary accent, active borders
COLOR_DARK=5018DD # Electric Violet — secondary accent, inactive borders
COLOR_RED=F50505 # Red Hi-vis — danger, alerts
COLOR_TEXT=D6ABAB # Quasi-White Text — foreground, labels; slightly warm/muted to reduce eye strain
COLOR_BG=1A1A1A # Gray Background — near-black base surface; dark enough to make colors pop
COLOR_HIGHLIGHT=E40046 # Hot Pink Accent — primary accent used for active borders, selections, and highlights
COLOR_DARK=5018DD # Electric Violet — secondary accent used for inactive borders and informational segments
COLOR_RED=F50505 # Red Hi-vis — danger color for errors, alerts, and high-visibility UI elements

View File

@ -17,13 +17,20 @@ usage() {
URL="$1"
CUSTOM_NAME="${2:-}"
# Ensure scheme
# Prepend https:// if the user omitted the scheme, so curl/grep work correctly.
[[ "$URL" != http://* && "$URL" != https://* ]] && URL="https://$URL"
# Extract just the origin (scheme + host) — needed as a base for resolving
# relative favicon URLs later (e.g. "/icons/logo.png" → "https://example.com/icons/logo.png").
BASE_URL=$(echo "$URL" | grep -oE 'https?://[^/]+')
# Strip scheme and optional "www." to get a clean domain used as a fallback name.
DOMAIN=$(echo "$BASE_URL" | sed -E 's|https?://(www\.)?||')
echo "Fetching: $URL"
# -sL : silent + follow redirects. -A : spoof a browser User-Agent so sites
# don't return a stripped mobile/bot page with no <title> or <link> tags.
# The "|| echo ''" prevents pipefail from killing the script on network errors.
PAGE_HTML=$(curl -sL --max-time 15 \
-A "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" \
"$URL" 2>/dev/null || echo "")
@ -32,6 +39,8 @@ PAGE_HTML=$(curl -sL --max-time 15 \
if [[ -n "$CUSTOM_NAME" ]]; then
APP_NAME="$CUSTOM_NAME"
else
# Python inline: parse the <title> tag and HTML-unescape entities like &amp;
# so the .desktop Name field looks clean. Falls back to DOMAIN on failure.
APP_NAME=$(echo "$PAGE_HTML" | python3 -c '
import sys, re, html as html_mod
content = sys.stdin.read()
@ -41,11 +50,18 @@ print(html_mod.unescape(m.group(1).strip()) if m else "", end="")
APP_NAME="${APP_NAME:-$DOMAIN}"
fi
# Safe identifier for filenames / WM_CLASS
# Build a filesystem-safe identifier from the app name.
# tr '[:upper:]' '[:lower:]' : lowercase everything
# tr -cs 'a-z0-9' '-' : replace any run of non-alphanumeric chars with '-'
# sed 's/^-*//;s/-*$//' : trim leading/trailing hyphens
# Used for both the .desktop filename and the StartupWMClass (WM grouping key).
SAFE_ID=$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-' | sed 's/^-*//;s/-*$//')
[[ -z "$SAFE_ID" ]] && SAFE_ID="$DOMAIN"
# Find best favicon from HTML link tags (largest declared size wins)
# Find the best favicon declared in <link rel="icon"> tags.
# The Python snippet ranks candidates by their declared pixel size (sizes="NxN")
# and picks the largest — "any" is treated as 999 to rank SVG/vector icons highest.
# urljoin resolves relative paths against BASE_URL, matching browser behavior.
FAVICON_URL=$(echo "$PAGE_HTML" | python3 -c '
import sys, re
from urllib.parse import urljoin
@ -76,24 +92,31 @@ if candidates:
print(urljoin(base, candidates[0][1]), end="")
' "$BASE_URL" 2>/dev/null || true)
# Fall back to the conventional /favicon.ico path when no <link> tag was found.
FAVICON_URL="${FAVICON_URL:-$BASE_URL/favicon.ico}"
echo "Favicon: $FAVICON_URL"
# Download icon
# Store webapp icons separately from system icons to keep ~/.local/share/icons clean.
ICON_DIR="$HOME/.local/share/icons/webapps"
mkdir -p "$ICON_DIR"
# Use a temp file so a failed download doesn't leave a partial file at the final path.
TMP=$(mktemp /tmp/webapp-icon.XXXXXX)
trap 'rm -f "$TMP"' EXIT
# Default: fall through to the chromium themed icon if download/convert fails.
ICON_PATH="chromium" # fallback
if curl -sL --max-time 10 -A "Mozilla/5.0" -o "$TMP" "$FAVICON_URL" && [[ -s "$TMP" ]]; then
# Prefer ImageMagick: convert normalizes the format (handles multi-frame .ico
# via [0]) and caps the size at 128x128 without upscaling (\> flag).
if command -v convert &>/dev/null \
&& convert "${TMP}[0]" -resize 128x128\> "${ICON_DIR}/${SAFE_ID}.png" 2>/dev/null; then
ICON_PATH="${ICON_DIR}/${SAFE_ID}.png"
echo "Icon: $ICON_PATH (PNG via ImageMagick)"
else
# Fallback: keep the file as-is, just fix the .ico MIME type alias so the
# extension matches what XDG icon loaders expect.
MIME=$(file -b --mime-type "$TMP")
EXT="${MIME##*/}"
[[ "$EXT" == "x-icon" || "$EXT" == "vnd.microsoft.icon" ]] && EXT="ico"
@ -105,7 +128,9 @@ else
echo "Warning: could not fetch favicon — using chromium default icon"
fi
# Write .desktop file
# Write .desktop file per freedesktop.org Desktop Entry Specification.
# chromium --app=<url> opens the site in an app window (no address bar/tabs),
# making it feel like a native application.
DESKTOP_DIR="$HOME/.local/share/applications"
mkdir -p "$DESKTOP_DIR"
DESKTOP_FILE="${DESKTOP_DIR}/webapp-${SAFE_ID}.desktop"
@ -122,6 +147,7 @@ Categories=Network;WebBrowser;
StartupWMClass=${SAFE_ID}
DESKTOP
# Mark executable so the file manager and XDG launchers treat it as launchable.
chmod +x "$DESKTOP_FILE"
echo

View File

@ -1,4 +1,11 @@
#!/usr/bin/env bash
# Decrypt a base64-encoded AES-256-CBC ciphertext produced by encrypt.sh.
# Usage: decrypt.sh <ciphertext> <passphrase>
# -d : decrypt mode (reverse of encryption)
# -a : base64-decode the input first; must mirror the -a used at encrypt time
# -pbkdf2 : derive the symmetric key via PBKDF2 rather than the deprecated EVP_BytesToKey;
# openssl will error if this flag doesn't match what was used to encrypt
# -pass pass : accept the passphrase directly from the CLI argument $2
echo $1 | openssl aes-256-cbc -d -a -pbkdf2 -pass pass:$2

View File

@ -0,0 +1,61 @@
# ============================================================================
# application-style.conf — hyprtoolkit / QT application colour palette
#
# Defines the named colour tokens for hyprtoolkit-aware applications so that
# QT apps respect the same cyberqueer colour palette used in eww, hyprlock,
# and GTK. Values here match the border colours, eww SCSS variables, and
# hyprlock label colours throughout the rest of the Hyprland config stack.
# ============================================================================
# background: deep near-black (#1A1A1A) — primary window/widget background.
# This matches the eww bar background and hyprlock inner-field colour.
background = rgb(1A1A1A)
# base: electric blue (#5018DD) — base/secondary brand colour.
# Used for secondary UI elements, inactive borders, and muted accents.
base = rgb(5018DD)
# font_family: Agave Nerd Font Mono — used everywhere for consistent glyph
# support (Nerd Font icons, powerline symbols) and a monospace coding aesthetic.
font_family = Agave Nerd Font Mono
# font_family_monospace: explicit monospace fallback (same as font_family).
font_family_monospace = Agave Nerd Font Mono
# text: muted mauve-grey (#D6ABAB) — body text colour.
# Readable on the dark background without the harshness of pure white.
text = rgb(D6ABAB)
# alternate_base: primary accent — hot crimson red (#E40046).
# Used for selected items, highlighted elements, and active states.
alternate_base = rgb(E40046)
# bright_text: vivid red (#F50505) — high-contrast emphasis colour.
# Used for alerts, errors, or very active UI elements.
bright_text = rgb(F50505)
# accent: primary brand accent (same crimson red as alternate_base).
# Kept separate so apps can theme the main accent independently.
accent = rgb(E40046)
# accent_secondary: secondary accent — same electric blue as `base`.
# Provides a blue counterpoint to the red primary accent.
accent_secondary = rgb(5018DD)
# icon_theme: Papirus provides a clean icon set that works well with dark
# backgrounds and the blue/red palette.
icon_theme = Papirus
# h1_size: heading font sizes. Note: h1_size is declared three times;
# in most parsers the last value (30) wins. The sequence 15, 20, 30
# appears to be a progressive heading-level definition where only the
# last one takes effect as the h1 size.
h1_size = 15
h1_size = 20
h1_size = 30
# font_size: base body font size in points. At 2× HiDPI this renders as ~20pt.
font_size = 10
# small_font_size: reduced size for secondary/caption text.
small_font_size = 5

View File

@ -1,21 +1,61 @@
#source ~/.config/idle.conf
# ============================================================================
# hypridle.conf — Idle action configuration for hypridle
#
# hypridle watches for user inactivity and fires timed actions.
# This config handles two tiers of idle behaviour:
# 1. Screen lock (short idle — 2 minutes)
# 2. Suspend-then-hibernate (longer idle — 10 minutes)
#
# The "caffeine" toggle (scripts/caffeine.sh) kills hypridle to prevent
# any of these actions when you want to keep the session awake.
# ============================================================================
#source ~/.config/idle.conf # Alternate source path (disabled)
# ── general: session-level commands ────────────────────────────────────────
general {
# lock_cmd: command to run when the session is asked to lock.
# `pidof hyprlock || hyprlock` prevents a second hyprlock instance if one
# is already running — important because loginctl can call this repeatedly.
lock_cmd = pidof hyprlock || hyprlock # avoid starting multiple hyprlock instances.
# before_sleep_cmd: runs immediately before the system suspends.
# loginctl lock-session triggers the PAM lock mechanism, which calls lock_cmd above.
# This ensures the screen is locked before suspend so wake-up requires auth.
before_sleep_cmd = loginctl lock-session # lock before suspend.
# after_sleep_cmd: runs after the system resumes from suspend.
# fprintd is the fingerprint authentication daemon — restarting it after
# resume ensures the fingerprint reader is re-initialised (common driver quirk).
# `hyprctl dispatch dpms on` forces the display back on without requiring
# a key press (avoids the "press a key twice" issue).
after_sleep_cmd = systemctl restart fprintd.service ;; hyprctl dispatch dpms on # to avoid having to press a key twice to turn on the display.
}
# ── listener 1: lock screen after 2 minutes of inactivity ──────────────────
listener {
# timeout: seconds of inactivity before the action fires. 120s = 2 minutes.
# Short timeout to quickly protect the session when stepping away briefly.
timeout = 120
# on-timeout: lock the session via loginctl (which invokes lock_cmd above).
on-timeout = loginctl lock-session # lock screen when timeout has passed
}
# ── listener 2: suspend after 10 minutes of inactivity ─────────────────────
listener {
# timeout: 600s = 10 minutes. After the screen has been locked for ~8 more
# minutes with no activity, the system suspends to save power.
timeout = 600 #10min
# on-timeout: suspend-then-hibernate writes RAM to disk and suspends.
# If not woken within the hibernate delay, the system fully hibernates.
on-timeout = systemctl suspend-then-hibernate # suspend pc
}
# ── Disabled: reboot after 5 hours ─────────────────────────────────────────
# Commented out — a forced reboot after 5 hours is too aggressive for a
# personal workstation. Kept as reference in case needed for kiosk use.
#listener {
# timeout = 18000 #5h
# on-timeout = /usr/bin/reboot #reboot

View File

@ -1,9 +1,31 @@
source = ~/.config/input.conf
source = ~/.config/monitors.conf
source = ~/.config/envvars.conf
source = ~/.config/binds.conf
source = ~/.config/windowrules.conf
source = ~/.config/autostart.conf
# ============================================================================
# hyprland.conf — Main Hyprland compositor configuration
#
# This is the entry point for the Hyprland window manager config. It sources
# all per-user split configs (input, monitors, env vars, keybinds, etc.) from
# ~/.config/ symlinks so that user preferences live in one place and are
# independent of the DE source tree.
#
# Split config pattern: instead of one huge file, settings are broken into
# focused files (input.conf, binds.conf, etc.) and loaded via `source =`.
# The files in hypr-usr/ are symlinked to ~/.config/ during setup.
# ============================================================================
# ── Split config sources ────────────────────────────────────────────────────
# Each `source` directive merges the named file into this config at runtime.
# These files live under hypr-usr/ and are symlinked into ~/.config/ by the
# setup scripts, allowing machine-local overrides without touching the repo.
source = ~/.config/input.conf # Keyboard layout, mouse sensitivity, touchpad
source = ~/.config/monitors.conf # Monitor resolution, scale, rotation rules
source = ~/.config/envvars.conf # Wayland/GTK/QT environment variable exports
source = ~/.config/binds.conf # All keybindings and gestures
source = ~/.config/windowrules.conf # Per-app window placement and behaviour rules
source = ~/.config/autostart.conf # exec-once applications started with the session
# ── Disabled hyprexpo plugin (commented out) ───────────────────────────────
# hyprexpo provides a grid overview of all workspaces (like GNOME Activities).
# Disabled because the gesture-based workspace navigation in binds.conf already
# covers this use case without requiring a compiled plugin.
#plugin {
# hyprexpo {
# columns = 3
@ -21,9 +43,12 @@ source = ~/.config/autostart.conf
# Eample per-device config
# See https://wiki.hyprland.org/Configuring/Keywords/#per-device-input-configs for more
# Per-device overrides let you set sensitivity for a specific mouse independently
# of the global input sensitivity. "epic-mouse-v1" is the Hyprland device id;
# run `hyprctl devices` to find the correct identifier for connected hardware.
device {
name = epic-mouse-v1
sensitivity = -0.5
sensitivity = -0.5 # Negative = faster pointer; 0 = raw, no acceleration applied
}
@ -46,10 +71,12 @@ device {
# See https://wiki.hyprland.org/Configuring/Keywords/
# Set programs that you use
$terminal = kitty
$fileManager = kitty -e yazi
$editor = kitty micro
$menu = wofi --show=drun
# These $variables are referenced in keybindings (binds.conf) so changing the
# program here automatically updates every keybind that uses it.
$terminal = kitty # Primary terminal emulator
$fileManager = kitty -e yazi # TUI file manager (yazi) launched inside kitty
$editor = kitty micro # Text editor (micro) launched in a terminal window
$menu = wofi --show=drun # Application launcher — overridden by vicinae in binds.conf
#################
@ -57,6 +84,8 @@ $menu = wofi --show=drun
#################
# Autostart necessary processes (like notifications daemons, status bars, etc.)
# Actual exec-once lines are in ~/.config/autostart.conf (sourced above).
# This section header is kept here as a readability landmark.
@ -67,114 +96,190 @@ $menu = wofi --show=drun
# Refer to https://wiki.hyprland.org/Configuring/Variables/
# ── general: tiling gaps, borders, and layout ──────────────────────────────
# https://wiki.hyprland.org/Configuring/Variables/#general
general {
general {
# gaps_in: space (px) between individual tiled windows inside a workspace.
# Small value keeps windows visually separated without wasting screen space.
gaps_in = 3
# gaps_out: space (px) between the outermost windows and the monitor edge.
# Slightly larger than gaps_in so the screen frame is clearly visible.
gaps_out = 6
# border_size: width of the coloured border drawn around each window (px).
# 4 px is prominent on a high-DPI display and reinforces the active window.
border_size = 4
# https://wiki.hyprland.org/Configuring/Variables/#variable-types for info about colors
# col.active_border: gradient applied to the focused window's border.
# Multi-stop gradient: primary red (#E40046) → deeper red (#f50505) at 35°,
# creating a crimson animated border matching the cyberqueer colour theme.
#col.active_border = rgb(E40046) rgb(fc0588) 40deg
col.active_border = rgb(E40046) rgb(f50505) rgb(E40046) rgb(f50505) rgb(E40046) 35deg
# col.inactive_border: solid electric blue (#5018DD) for unfocused windows.
# This is the secondary accent colour of the cyberqueer palette.
col.inactive_border = rgb(5018dd)
# Set to true enable resizing windows by clicking and dragging on borders and gaps
resize_on_border = false
# Disabled because it conflicts with gap-hover interactions on touch screens.
resize_on_border = false
# Please see https://wiki.hyprland.org/Configuring/Tearing/ before you turn this on
# allow_tearing permits screen tearing for lower latency in games.
# Disabled — visual quality preferred; enable per-app via windowrule if needed.
allow_tearing = false
# layout: default tiling algorithm for new windows.
# "dwindle" splits the screen recursively (like bspwm).
# Toggle between dwindle/master at runtime via toggle-layout.sh.
layout = dwindle
}
# ── group: window grouping (tabbed windows) ─────────────────────────────────
# Groups let multiple windows share the same screen tile, switchable via a tab bar.
group {
col.border_active = rgb(E40046)
col.border_inactive = rgb(5018dd)
col.border_locked_active = rgb(f50505)
col.border_locked_inactive = rgb(5018dd)
# Border colours for grouped windows mirror the main active/inactive palette.
col.border_active = rgb(E40046) # Hot pink/red — focused group border
col.border_inactive = rgb(5018dd) # Electric blue — unfocused group border
col.border_locked_active = rgb(f50505) # Deeper red — locked (pinned) active group
col.border_locked_inactive = rgb(5018dd) # Same blue for locked inactive group
#rounding = 10
#rounding = 10 # Commented — would override global rounding just for groups
# groupbar: the tab strip shown above grouped windows.
groupbar {
# font_family: Agave NerdFont provides the Nerd Font icon glyphs used
# throughout the system (bar, lock screen, etc.).
font_family = Agave NerdFont
# font_size: 20pt readable at 2× HiDPI scaling without being too large.
font_size = 20
# height: total height of the groupbar strip in pixels.
height = 25
# round_only_edges = false means all corners are rounded, not just outer.
round_only_edges = false
# indicator_height: height of the coloured active-tab underline indicator.
indicator_height = 25
# stacked = false: tabs displayed horizontally side-by-side (not vertically).
stacked = false
# text_color: tab label colour — primary accent red.
text_color = rgb(E40046)
# priority: render order vs other decorations (shadows etc.).
# 3 = high priority so the groupbar appears on top of other decorations.
priority = 3
# rounding: corner radius for the tab bar itself — pill-shaped appearance.
rounding = 13
# Active tab background — red accent to show which tab is selected.
col.active = rgb(E40046)
# Inactive tab backgrounds — blue to distinguish non-selected tabs.
col.inactive = rgb(5018dd)
# Locked group tab colours (same as unlocked for visual consistency).
col.locked_active = rgb(E40046)
col.locked_inactive = rgb(5018dd)
}
}
# ── decoration: window rounding, transparency, and blur ─────────────────────
# https://wiki.hyprland.org/Configuring/Variables/#decoration
decoration {
# rounding: window corner radius in pixels. 20px gives a modern, rounded
# look consistent with the GTK cyberqueer theme's rounded widgets.
rounding = 20
# Change transparency of focused and unfocused windows
# active_opacity: focused window is fully opaque (1.0 = 100%).
# Keeps the active window readable while inactive windows recede visually.
active_opacity = 1
# inactive_opacity: unfocused windows are 80% opaque so the blurred
# wallpaper shows through slightly, creating depth perception.
inactive_opacity = 0.8
# Drop shadow disabled: coloured borders already provide sufficient depth cues.
#drop_shadow = true
#shadow_range = 4
#shadow_render_power = 3
#col.shadow = rgba(1a1a1aee)
# https://wiki.hyprland.org/Configuring/Variables/#blur
# blur: Gaussian-style background blur behind transparent/inactive windows.
blur {
enabled = true
# size: blur kernel radius — 3 is subtle and not GPU-heavy.
size = 3
# passes: number of blur iterations — 3 gives a smooth result without
# excessive GPU cost. More passes = smoother blur but heavier rendering.
passes = 3
# vibrancy: boosts colour saturation of the blurred background content.
# 0.1696 ≈ 17% vibrancy — subtle colour pop behind transparent windows.
vibrancy = 0.1696
}
}
# ── animations ──────────────────────────────────────────────────────────────
# https://wiki.hyprland.org/Configuring/Variables/#animations
animations {
enabled = true
# Default animations, see https://wiki.hyprland.org/Configuring/Animations/ for more
# bezier: defines a named cubic Bézier easing curve.
# myBezier (0.05, 0.9, 0.1, 1.05) — fast start, slight spring overshoot.
bezier = myBezier, 0.05, 0.9, 0.1, 1.05
# animation = <event>, <enabled 0/1>, <speed>, <curve>[, <style>]
# speed: lower = slower animation; style is an optional named effect.
# Window open/move: spring curve at speed 7 — snappy but not jarring.
animation = windows, 1, 7, myBezier
# Window close: "popin" effect shrinks the window from 80% size on close.
animation = windowsOut, 1, 7, default, popin 80%
# Border colour transition (gradient rotation): default easing, speed 10.
animation = border, 1, 10, default
# Border angle animation (rotating gradient sweep): speed 8.
animation = borderangle, 1, 8, default
# Fade in/out for opacity transitions: speed 7.
animation = fade, 1, 7, default
# Workspace slide animations are disabled — vertical slide is used for the
# special/scratchpad workspace only (see specialWorkspace below).
#animation = workspaces, 1, 6, default
#animation = workspaces,1,5,default,slidevert
# Special workspace (scratchpad "magic"): slides in/out vertically at speed 10.
animation = specialWorkspace, 1, 10, default, slidevert
}
}
# ── dwindle layout ──────────────────────────────────────────────────────────
# See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more
dwindle {
#pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
# preserve_split: keeps the split direction when a window is removed.
# Without this, removing a child window can unexpectedly flip the split axis.
preserve_split = true # You probably want this
# special_scale_factor: scratchpad workspace windows fill 95% of the monitor,
# leaving a small visible border around the floating overlay.
special_scale_factor = 0.95
}
# ── master layout ───────────────────────────────────────────────────────────
# See https://wiki.hyprland.org/Configuring/Master-Layout/ for more
master {
# new_status: new windows open as the "master" (primary, largest) pane.
# Alternative value "slave" would stack new windows alongside the master.
new_status = master
# special_scale_factor: same 95% fill for the scratchpad in master mode.
special_scale_factor = 0.95
}
# ── misc ────────────────────────────────────────────────────────────────────
# https://wiki.hyprland.org/Configuring/Variables/#misc
misc {
misc {
force_default_wallpaper = 0 # Set to 0 or 1 to disable the anime mascot wallpapers
# 0 = disable Hyprland's built-in wallpaper; hyprpaper manages wallpapers instead.
disable_hyprland_logo = true # If true disables the random hyprland logo / anime girl background. :(
}

View File

@ -1,208 +1,399 @@
// ============================================================
// Waybar Config — Hyprland Cyberqueer Setup
// ============================================================
// Waybar is the taskbar/statusbar displayed at the top of the
// Hyprland Wayland compositor. This JSON5 file (comments and
// trailing commas allowed) defines every module shown on the
// bar: position, update intervals, display formats, click
// actions, and hover tooltips.
//
// Cyberqueer color palette (referenced throughout):
// #1a1a1a — near-black background
// #5018dd — deep electric purple (inactive / cool accent)
// #E40046 — hot neon red/pink (active / warm accent)
// #d6abab — muted dusty rose (readable text / neutral)
// #f50505 — bright red (critical / alert states)
// ============================================================
{
// ── Bar placement ──────────────────────────────────────────
// "layer": "top" — the bar sits above all other windows so
// it is never covered by maximized applications.
// "position": "top" — anchored to the top edge of the screen.
"layer": "top", "position": "top",
// ── Module layout ──────────────────────────────────────────
// Waybar divides the bar into three zones: left, center, right.
// The order within each list is the left-to-right display order.
"modules-left": ["clock", "disk", "memory", "cpu", "temperature" ],
// Center: Hyprland workspace switcher + currently focused window title.
"modules-center": [ "hyprland/workspaces", "hyprland/window"],
// Right: network icon, local IP, system tray, audio volume, battery.
"modules-right": [ "network", "custom/netaddrsimple", "tray", "pulseaudio", "battery" ],
// Automatically reload style.css when the file changes on disk,
// so appearance tweaks take effect without restarting waybar.
"reload_style_on_change":true,
// ── custom/netaddrsimple ───────────────────────────────────
// A bare-minimum custom module that shows the machine's local IP.
// Handy for quickly finding the address to SSH into.
"custom/netaddrsimple": {
// {} is substituted with the stdout of the exec command.
"format": "IP:{}",
// No tooltip needed — the IP text in the bar is sufficient.
"tooltip":false,
// (Commented out) would limit the displayed text to 15 characters.
//"max-length": 15,
// Refresh every 10 seconds — local IP rarely changes.
"interval": 10,
"exec": "hostname -i",
// "hostname -i" prints the machine's primary local IP address.
"exec": "hostname -i",
},
// ── hyprland/workspaces ───────────────────────────────────
// Shows clickable workspace number buttons.
// Mouse-wheel scrolling switches workspaces sequentially.
"hyprland/workspaces": {
// Display the workspace name (typically its number) as label text.
"format": "{name}",
// These icon overrides replace the label when a workspace matches
// the "default", "active", or "urgent" state keys.
"format-icons": {
// Unfocused, non-urgent workspaces get a plain bullet.
"default": " ",
// The currently focused workspace is marked with "@".
"active": "@",
// A workspace with an urgent app (needs attention) shows "!".
"urgent": "!"
},
// Scroll wheel up → move to numerically higher workspace.
"on-scroll-up": "hyprctl dispatch workspace e+1",
// Scroll wheel down → move to numerically lower workspace.
"on-scroll-down": "hyprctl dispatch workspace e-1",
},
// NOTE: this clock block is structurally inside the workspaces
// object due to a brace mismatch in the original config.
// The top-level "clock" block defined further below takes
// precedence and is what waybar actually uses.
"clock": {
"format": "{:L%H:%M}",
// Locale-aware HH:MM format (24-hour).
"format": "{:L%H:%M}",
"tooltip": true,
// Tooltip shows a large date line and a small monthly calendar.
"tooltip-format": "<big>{:%A, %d.%B %Y }</big>\n<tt><small>{calendar}</small></tt>"
}
},
// ── idle_inhibitor ────────────────────────────────────────
// Prevents the system from going idle / blanking the screen
// while activated. Toggle by clicking the module.
"idle_inhibitor":{
// Wrap the icon in a larger font-size span so the glyph is readable.
"format": "<span font='12'>{icon} </span>",
"format-icons": {
// Nerd Font "eye open" — currently inhibiting idle / screen-off.
"activated":"󰈈",
// Nerd Font "eye closed" — idle allowed normally.
"deactivated":"󰈉"
}
},
// ── clock ─────────────────────────────────────────────────
// Real-time HH:MM:SS clock shown in the left section.
// Updates every second so the seconds digit stays live.
"clock": {
// Show hours, minutes, and seconds with a trailing space.
"format": "{:%H:%M:%S }",
// 1-second interval to keep the clock accurate to the second.
"interval":1,
// Tooltip: a large date header plus a mini monthly calendar.
"tooltip-format": "\n<big>{:%d %m %Y}</big>\n<tt><small>{calendar}</small></tt>",
// Display ISO week numbers on the right side of the calendar grid.
"calendar-weeks-pos": "right",
// Highlight today's date cell in the cyberqueer purple (#7645AD).
"today-format": "<span color='#7645AD'><b><u>{}</u></b></span>",
// Calendar day numbers in muted grey so they don't compete visually.
"format-calendar": "<span color='#aeaeae'><b>{}</b></span>",
// Week-number column label (e.g. "W24") in the same muted grey.
"format-calendar-weeks": "<span color='#aeaeae'><b>W{:%V}</b></span>",
// Weekday header row (Mon, Tue…) in muted grey.
"format-calendar-weekdays": "<span color='#aeaeae'><b>{}</b></span>"
},
// ── bluetooth ────────────────────────────────────────────
// Bluetooth state indicator using Nerd Font glyphs.
// Clicking opens rofi-bluetooth for pairing/device management.
"bluetooth": {
"format-on": "",
// Bluetooth is on but nothing is connected.
"format-on": "",
// Bluetooth is turned off.
"format-off": "",
// Bluetooth hardware is disabled at the kernel/driver level.
"format-disabled": "󰂲",
// At least one device is connected.
"format-connected": "󰂴",
// Connected device reports battery level — show it alongside the icon.
"format-connected-battery": "{device_battery_percentage}% 󰂴",
// Base tooltip: controller name, MAC, and connection count.
"tooltip-format": "{controller_alias}\t{controller_address}\n\n{num_connections} connected",
// Extended tooltip listing each connected device when count > 0.
"tooltip-format-connected": "{controller_alias}\t{controller_address}\n\n{num_connections} connected\n\n{device_enumerate}",
// Per-device line in the enumeration: alias + MAC.
"tooltip-format-enumerate-connected": "{device_alias}\t{device_address}",
// Per-device line when battery info is available.
"tooltip-format-enumerate-connected-battery": "{device_alias}\t{device_address}\t{device_battery_percentage}%",
// Left-click launches rofi-bluetooth for a GUI device list.
"on-click": "rofi-bluetooth",
},
},
// ── battery ─────────────────────────────────────────────
// Shows remaining battery capacity and charging state.
// 1-second interval so the "PWR-" / "PWR+" prefix switches instantly.
"battery": {
"interval":1,
// Named thresholds that trigger CSS state classes:
// .good ≥ 95% (full — no visual warning)
// .warning ≤ 30% (getting low)
// .critical ≤ 20% (nearly empty — style.css colors this bright red)
"states": {
"good": 95,
"warning": 30,
"critical": 20
},
// Discharging: "PWR-" prefix + capacity + level icon.
"format": "PWR- {capacity}% {icon} ",
// Charging via AC: "PWR+" prefix + the charging glyph.
"format-charging": "PWR+ {capacity}% 󰂄 ",
"format-plugged": "PWR+ {capacity}% 󰂄 ",
// Plugged in at 100%: show charger icon even though full.
"format-plugged": "PWR+ {capacity}% 󰂄 ",
// Right-click (alt format) shows estimated time remaining.
"format-alt": "{time} {icon}",
// Six Nerd Font battery glyphs mapping roughly to 0→100% in steps.
"format-icons": [
"󰁻",
"󰁼",
"󰁾",
"󰂀",
"󰂂",
"󰁹"
"󰁻", // 020 %
"󰁼", // 2040 %
"󰁾", // 4060 %
"󰂀", // 6080 %
"󰂂", // 8095 %
"󰁹" // 95100 %
],
},
// ── backlight ──────────────────────────────────────────────
// Screen brightness via the intel_backlight sysfs interface.
// Scroll wheel on this module adjusts brightness by ±10 units.
"backlight": {
// Target the Intel integrated GPU backlight node specifically.
"device": "intel_backlight",
// Display only the icon — brightness level communicated visually.
"format": "<span font='12'>{icon}</span>",
// Ten sun/brightness Nerd Font icons mapping 0%→100% in 10% steps.
"format-icons": [
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"", // 010 %
"", // 1020 %
"", // 2030 %
"", // 3040 %
"", // 4050 %
"", // 5060 %
"", // 6070 %
"", // 7080 %
"", // 8090 %
"", // 90100 %
],
// Scroll down decreases brightness (note: flag names are swapped in `light`).
"on-scroll-down": "light -A 10",
// Scroll up increases brightness.
"on-scroll-up": "light -U 10",
// Threshold of 1 means no acceleration — every scroll tick = 10 units.
"smooth-scrolling-threshold": 1
},
// ── disk ─────────────────────────────────────────────────
// Shows how much of the root partition is used, as a percentage.
// 30-second interval is fine — disk usage changes slowly.
"disk": {
"interval": 30,
"format": " {percentage_used}%",
// Hard-drive Nerd Font glyph + used-space percentage.
"format": " {percentage_used}%",
// Watch the root filesystem specifically (not /home or other mounts).
"path": "/"
},
// ── custom/colorpicker ───────────────────────────────────
// Wraps hyprpicker into a waybar button.
// Clicking launches the eyedropper; the picked hex is copied to
// clipboard and the module refreshes to show a colored dot.
"custom/colorpicker": {
// {} is replaced with the JSON "text" field from the script.
"format": "{}",
// Script returns {text, tooltip} JSON — waybar parses both fields.
"return-type": "json",
// "once" means only run at startup; RTMIN+1 triggers re-execution.
"interval": "once",
// On load: render the most-recently-picked color as a dot.
"exec": "~/.config/waybar/scripts/colorpicker.sh -j",
// Click: 1-second delay prevents the click from interfering with the
// hyprpicker window, then launch the picker.
"on-click": "sleep 1 && ~/.config/waybar/scripts/colorpicker.sh",
// colorpicker.sh sends RTMIN+1 to waybar after saving a new color,
// which causes this module to re-exec and show the new color.
"signal": 1
},
// ── cpu ──────────────────────────────────────────────────
// Aggregate CPU usage across all cores, updated every second.
"cpu": {
"interval": 1,
"format": " {usage}%",
// Nerd Font CPU chip icon + usage %. Fixed 6-char width stops the
// bar from shifting when the value goes from single to double digits.
"format": " {usage}%",
"min-length": 6,
"max-length": 6,
// Bar-graph Unicode chars available for use in custom format strings.
"format-icons": ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"],
},
// ── memory ────────────────────────────────────────────────
// RAM usage as a percentage. Compact single-field display.
"memory": {
"format": " {percentage}%"
// Memory chip Nerd Font glyph + percentage of RAM used.
"format": " {percentage}%"
},
// ── hyprland/window ──────────────────────────────────────
// Shows the WM_CLASS of the currently focused window in the bar center.
// Rewrite rules transform noisy titles into cleaner short labels.
"hyprland/window": {
// Wrap the class name in parentheses for a "terminal prompt" aesthetic.
"format": "( {class} )",
"rewrite": {
// Strip " - Mozilla Firefox" from page titles; prepend a globe.
"(.*) - Mozilla Firefox": "🌎 $1",
// Kitty/foot terminal: show the current directory in brackets.
"(.*) - zsh": "> [$1]"
}
},
// ── temperature ──────────────────────────────────────────
// CPU package temperature. Turns to a "critical" CSS class above 80 °C.
// Clicking opens btop for a full system-monitoring view.
"temperature": {
"format": " {temperatureC}°C",
"format-critical": " {temperatureC}°C",
// Normal state: thermometer glyph + temperature in Celsius.
"format": " {temperatureC}°C",
// Critical state: fire glyph replaces the thermometer icon.
"format-critical": " {temperatureC}°C",
// 1-second interval to catch sudden thermal spikes quickly.
"interval": 1,
// Above 80 °C the module switches to the "critical" CSS class.
"critical-threshold": 80,
// Left-click opens btop in the foot Wayland terminal.
"on-click": "foot btop",
},
// ── pulseaudio ───────────────────────────────────────────
// Volume level and mute control via PulseAudio or PipeWire-pulse.
// Left-click: toggle mute. Right-click: open pavucontrol GUI mixer.
"pulseaudio": {
// Default format: percentage + device-type icon.
"format": "{volume}% {icon}",
// When the active sink is a Bluetooth device, show a BT-speaker glyph.
"format-bluetooth":"󰂰",
"format-muted": "<span font='12'></span>",
// Muted: show a crossed-speaker glyph, hide the volume number.
"format-muted": "<span font='12'></span>",
// Icon set keyed by audio device type reported by PulseAudio.
"format-icons": {
"headphones": "",
"headphones": "",
"bluetooth": "󰥰",
"handsfree": "",
"handsfree": "",
"headset": "󱡬",
"phone": "",
"portable": "",
"car": "",
"phone": "",
"portable": "",
"car": "",
// Default (built-in speaker): three-tier glyphs for low/mid/high volume.
"default": ["󰕿","󰖀","󰕾"]
},
// Center-align the label within the module's allocated width.
"justify": "center",
// Toggle Master channel mute via amixer on left-click.
"on-click": "amixer sset Master toggle",
// Open the graphical PulseAudio volume control on right-click.
"on-click-right": "pavucontrol",
// Tooltip shows icon + volume % for a quick glance.
"tooltip-format": "{icon} {volume}%"
},
// ── jack ─────────────────────────────────────────────────
// JACK Audio Connection Kit DSP load monitor.
// Relevant when running pro-audio software that uses JACK directly.
"jack": {
// {} = current DSP percentage (how busy the JACK graph is).
"format": "{} 󱎔",
// Xruns are buffer underruns — audio glitches. Non-zero = problem.
"format-xrun": "{xruns} xruns",
// JACK server is not running.
"format-disconnected": "DSP off",
// Poll in real-time (rather than on an interval) for accuracy.
"realtime": true
},
// ── tray ────────────────────────────────────────────────
// System tray using the StatusNotifierItem / libdbusmenu protocol.
// Apps like NetworkManager applet, Blueman, etc. dock here.
"tray": {
// Icon render size in pixels — 14 px is compact but readable.
"icon-size": 14,
// Horizontal gap between tray icons in pixels.
"spacing": 10
},
// ── upower ──────────────────────────────────────────────
// Displays UPower-tracked peripheral battery levels (mice, keyboards).
// The icon is suppressed; info appears only in the tooltip to save space.
"upower": {
// Don't show a persistent icon in the bar.
"show-icon": false,
// Collapse the module entirely when no UPower devices are present.
"hide-if-empty": true,
"tooltip": true,
// Vertical spacing between device entries in the tooltip.
"tooltip-spacing": 20
},
// ── network ─────────────────────────────────────────────
// Connection-type indicator: one icon for wifi, one for ethernet,
// one for disconnected. Hovering reveals SSID, signal, or IP.
"network":{
"format-wifi": " ",
"format-ethernet":" ",
"format-disconnected": "",
// Wireless: show only a wifi Nerd Font glyph (no text).
"format-wifi": " ",
// Wired: ethernet cable Nerd Font glyph.
"format-ethernet":" ",
// Not connected to anything.
"format-disconnected": "",
// Ethernet tooltip: just the interface name (eth0, enp3s0, etc.).
"tooltip-format": "{ifname}",
"tooltip-format-wifi": "{essid} ({signalStrength}%)  | {ipaddr}",
// Wi-Fi tooltip: SSID, signal percentage, and IP address.
"tooltip-format-wifi": "{essid} ({signalStrength}%) | {ipaddr}",
// Ethernet tooltip: interface name and IP address.
"tooltip-format-ethernet": "{ifname} 🖧 | {ipaddr}"
},
// ── custom/powerDraw ─────────────────────────────────────
// Reads instantaneous battery power draw in watts from sysfs.
// Useful for identifying rogue processes spiking power consumption.
"custom/powerDraw": {
// {} replaced by the JSON "text" field from the script output.
"format": "{}",
// Poll every second — power draw is volatile.
"interval": 1,
// Script reads /sys/class/power_supply/BAT*/power_now and formats JSON.
"exec": "~/.config/waybar/scripts/powerdraw.sh",
// JSON return type lets the script provide both text and tooltip.
"return-type": "json"
}
}

View File

@ -1,58 +1,138 @@
/*
* ============================================================
* Waybar Style Hyprland Cyberqueer Theme
* ============================================================
* GTK CSS that controls the visual look of the waybar taskbar.
* All selectors match waybar's internal widget IDs.
*
* Cyberqueer palette:
* #1a1a1a near-black panel background
* #5018dd electric purple (inactive / default accent)
* #E40046 neon red-pink (active / urgent / focused)
* ============================================================
*/
/* Global reset
* Applied to every widget in the bar.
* Removes default GTK borders and sets transparent backgrounds
* so each widget can define its own background color below.
* Large border-radius (30 px) gives the "pill" capsule shape
* that is a signature of the cyberqueer aesthetic.
*/
* {
border: none;
/* Agave Nerd Font Mono provides the icon glyphs used in module
* format strings (battery, CPU, wifi, etc.). sans-serif is the
* generic fallback if Agave is not installed. */
font-family: Agave Nerd Font Mono, sans-serif;
font-size: 12pt;
/* Transparent default so color only appears where explicitly set. */
background: transparent;
/* Compact vertical padding keeps the bar slim. */
padding-top: 2px;
padding-bottom: 2px;
/* Horizontal padding gives each module text a bit of breathing room. */
padding-right: 6px;
padding-left: 6px;
/* 1 px top margin keeps module pills off the very top pixel. */
margin-top: 1px;
margin-bottom: 0px;
/* 2 px horizontal margin creates the small gap between adjacent modules. */
margin-right: 2px;
margin-left: 2px;
/* 30 px radius turns every element into a rounded pill shape. */
border-radius: 30px;
}
/* Workspace buttons inactive state
* Each workspace number is a GTK button.
* Inactive (non-focused) workspaces: dark background + purple text.
*/
#workspaces button {
/* 3 px solid border traces the pill outline clearly. */
border: solid;
border-width: 3px;
/* Near-black background matches the general panel color. */
background: #1a1a1a;
/* Electric purple for inactive workspace labels. */
color: #5018dd;
}
/* Workspace buttons active (focused) state
* The currently focused workspace switches text to neon red-pink
* so it immediately stands out from the inactive ones.
*/
#workspaces button.active {
border: solid;
border-width: 3px;
background: #1a1a1a;
/* Neon accent color makes the active workspace unmistakable. */
color: #E40046;
}
/* Workspace buttons urgent state
* An app on an urgent workspace is demanding attention.
* Colors are fully inverted: the hot accent becomes the background
* and dark text sits on top a high-contrast visual alarm.
*/
#workspaces button.urgent {
border: solid;
border-width: 3px;
/* Flipped: background becomes the neon accent. */
background: #E40046;
/* Dark text on bright background for maximum contrast. */
color: #1a1a1a;
}
/* Standard status modules
* All "regular" status modules share this single rule:
* dark pill background + electric-purple text. This creates a
* consistent look across the entire right side of the bar.
*
* #clock HH:MM:SS time
* #disk root disk usage %
* #window focused window class/title
* #battery battery level and charge state
* #cpu CPU usage %
* #memory RAM usage %
* #temperature CPU temperature
* #backlight screen brightness
* #network network connection type
* #pulseaudio audio volume / mute state
* #custom-media media player (if module is added)
* #tray system tray host
* #mode Hyprland submap mode label
* #idle_inhibitor idle/screen-lock prevention toggle
* #custom-netaddrsimple local IP address display
*/
#clock, #disk, #window, #battery, #cpu, #memory, #temperature, #backlight, #network, #pulseaudio, #custom-media, #tray, #mode, #idle_inhibitor, #custom-netaddrsimple {
border: solid;
border-width: 3px;
/* Dark panel background shared across all status modules. */
background: #1a1a1a;
/* Electric purple for all default module text and Nerd Font icons. */
color: #5018dd;
}
/* idle_inhibitor activated (idle prevention ON)
* When the user has clicked to prevent the screen from sleeping,
* switch to the neon accent so it is visually obvious that
* idle inhibition is currently active.
*/
#idle_inhibitor.activated {
border: solid;
border-width: 3px;
background: #1a1a1a;
/* Neon red-pink = "something is actively overriding idle". */
color: #E40046;
}

View File

@ -1,4 +1,23 @@
; =============================================================================
; eww.yuck — EWW bar configuration for hyprlua (laptop with battery variant)
; This file defines all widgets, polls, and listeners for the top status bar.
; EWW (Elkowar's Wacky Widgets) is a widget system for Wayland/X11 that uses
; a Lisp-like syntax (.yuck) for layout and SCSS for styling.
;
; Color palette (cyberqueer theme):
; Background: #1a1a1a (near-black)
; Accent 1: #E40046 (red — active/highlight)
; Accent 2: #5018dd (purple — inactive/secondary)
; Text: #D6ABAB (muted pink-grey)
; =============================================================================
; defwindow — declares the actual OS-level window that appears on screen.
; :monitor — which monitor index to render on (passed at launch time)
; :windowtype "dock" — tells the compositor this is a panel, so it reserves space
; :exclusive true — prevents other windows from overlapping the bar area
; :anchor "top center" — pins the bar to the top center of the monitor
; :geometry — sets position (x=0%, y=1% from top), width (99%), height (20px)
(defwindow bar [monitor]
:monitor monitor
:class "ewwbar"
@ -9,55 +28,88 @@
:height "20px"
:anchor "top center")
:exclusive true
; Render the bar widget, passing the monitor index through for workspace tracking
(bar :monitor_ monitor))
; defpoll — polls a shell command on an interval and stores the result.
; battery is polled every 2 seconds so the icon/percentage stays current.
; The script returns a formatted string like "󰁹 87%" for display.
(defpoll battery :interval "2s"
"~/Dotfiles/desktopenvs/hyprland/scripts/batteryperc")
; defwidget bar — root bar widget. Uses centerbox to split into three zones:
; left → winsworks (battery, workspaces, active window title)
; center → music (currently playing track)
; right → sidestuff (IP, volume, disk, caffeine, clock, systray)
(defwidget bar [monitor_]
(centerbox :orientation "h"
(winsworks :monitor monitor_)
(music)
(sidestuff)))
; winsworks — left section of the bar.
; Shows battery percentage, workspace buttons, and the active window title.
; :halign "start" — left-aligns the content within the centerbox left cell.
(defwidget winsworks [monitor]
(box :orientation "h" :space-evenly false :halign "start"
; Battery percentage badge — styled as a pill with class "music"
(box :class "music" {"${battery}"})
; Workspace dots — one button per active workspace on this monitor
(workspaceWidget :monitor monitor)
(button :onclick "~/Dotfiles/desktopenvs/hyprland/scripts/drawer.sh" :class "music" {" ${activewindow}"})
; Active window title — clicking opens the application drawer
(button :onclick "~/Dotfiles/desktopenvs/hyprland/scripts/drawer.sh" :class "music" {" ${activewindow}"})
)
)
; sidestuff — right section of the bar.
; :halign "end" — right-aligns all children within the centerbox right cell.
(defwidget sidestuff []
(box :class "sidestuff" :orientation "h" :space-evenly false :halign "end"
(box :class "music" {"󰛳 ${IP}"})
; IP address badge — refreshed every 5s, shows current LAN/VPN IP
(box :class "music" {"󰛳 ${IP}"})
; Volume slider — 󰓃 icon, drag to adjust, click to open pavucontrol mixer
; hyprctl eval is used to run Lua dispatch inline so the window gets the +mixer tag
(metric :label "󰓃"
:value volume
:onchange "pactl set-sink-volume @DEFAULT_SINK@ {}%"
:onclick "killall pavucontrol || hyprctl eval 'hl.dsp.exec_cmd(\"[tag +mixer] pavucontrol\")'")
(box
; Disk usage gauge — tooltip shows per-disk breakdown from dysk
; EWW_DISK is a built-in magic variable providing filesystem stats
(box
:tooltip {disks}
(metric :label ""
(metric :label ""
:value {round((1 - (EWW_DISK["/"].free / EWW_DISK["/"].total)) * 100, 0)}
:onchange ""
:onclick ""))
; Caffeine toggle button — shows ☕ when active, sleeping icon when off
(caffeine)
; Clock widget with calendar tooltip
(clock)
; System tray — renders applets from running applications (nm-applet, blueman, etc.)
(systray :class "music" :orientation "h" :spacing 2 :space-evenly true)
))
; music — center section: shows currently playing track via playerctl.
; Clicking play/pauses the current media player (spotify/vlc priority).
; Displays a music note icon + track name, or "None" if nothing is playing.
(defwidget music []
(button :class "music"
:orientation "h"
:space-evenly false
:halign "center"
:onclick "~/Dotfiles/desktopenvs/hyprland/scripts/playpause.sh"
{music != "" ? " ${music}" : " None"}))
{music != "" ? " ${music}" : " None"}))
; metric — reusable slider+label widget used for volume and disk usage.
; Parameters:
; label — icon/text shown as a clickable button
; value — current value (0-100)
; onchange — command run when slider is dragged (use {} as placeholder for value)
; onclick — command run when the label button is clicked
(defwidget metric [label value onchange onclick]
(box :orientation "h"
:class "metric"
@ -65,12 +117,21 @@
(button :class "label" :onclick onclick label)
(scale :min 0
:max 101
; Only make the slider interactive if an onchange command was provided
:active {onchange != ""}
:value value
:onchange onchange)))
; deflisten — subscribes to a long-running process that emits JSON on stdout.
; hyprland-workspaces is a helper binary that streams workspace state changes.
; The "_" argument means listen for all monitors; we filter by monitor in the widget.
(deflisten workspaces "hyprland-workspaces _")
; workspaceWidget — renders one button per workspace on the given monitor.
; :onscroll — scrolling the workspace area switches workspaces; sed converts
; "up"/"down" into "r+1"/"r-1" relative workspace references,
; then hyprctl eval runs the Lua dispatch inline.
; The special:magic scratchpad workspace gets a distinct icon (󱂬 without a number).
(defwidget workspaceWidget [monitor]
(eventbox :onscroll "hyprctl eval \"hl.dsp.focus({workspace='$(echo {} | sed 's/up/r+/;s/down/r-/')1'})\""
(box :class "workspaces"
@ -79,51 +140,75 @@
(for i in {workspaces[monitor].workspaces}
(button
:width 20
; Click a workspace button to focus it via Lua dispatch
:onclick "hyprctl eval 'hl.dsp.focus({workspace=${i.id}})'"
; i.class comes from hyprland-workspaces: "active", "occupied", or ""
:class "${i.class}"
{i.name == "special:magic" ? "󱂬" : "󱂬${i.name}"})))))
{i.name == "special:magic" ? "󱂬" : "󱂬${i.name}"})))))
; workspace-old — legacy workspace listener using a shell script instead of
; the hyprland-workspaces binary. Kept for reference/fallback.
(deflisten workspace-old "~/Dotfiles/desktopenvs/hyprland/scripts/workspace")
(defwidget workspaces-old []
(literal :content workspace-old))
; music poll — queries playerctl for the current track every 0.5 seconds.
; Returns an empty string when nothing is playing so the widget shows "None".
(defpoll music :interval "0.5s"
"~/Dotfiles/desktopenvs/hyprland/scripts/playerget")
; activewindow poll — gets the focused window title/class every 0.5 seconds.
; Used in the left section of the bar as a window title breadcrumb.
(defpoll activewindow :interval "0.5s"
"~/Dotfiles/desktopenvs/hyprland/scripts/activewindow")
; IP poll — gets the current IP address every 5 seconds.
; Slow interval because IP changes are infrequent; avoids unnecessary shell forks.
(defpoll IP :interval "5s"
"~/Dotfiles/desktopenvs/hyprland/scripts/ip")
; volume poll — reads the default audio sink volume every 0.5 seconds.
; Fast interval so the slider reflects hardware key presses immediately.
(defpoll volume :interval "0.5s"
"~/Dotfiles/desktopenvs/hyprland/scripts/getvol")
; time poll — reads the system clock every second for the clock widget.
; Format: HH:MM:SS|DD.MM.YYYY — the pipe character is used as a visual separator.
(defpoll time :interval "1s"
"date '+%H:%M:%S|%d.%m.%Y'")
; clock widget — displays the current time and shows a calendar as a tooltip.
; The tooltip is populated by the `calender` poll (note: intentional spelling).
(defwidget clock []
(box :class "clock"
:tooltip {calender}
(label :text {"${time}"})
(label :text {"${time}"})
)
)
; calender poll — runs a script that generates a text calendar (via cal).
; Polled every 600 seconds (10 minutes) since the calendar only changes daily.
(defpoll calender :interval "600s"
"~/Dotfiles/desktopenvs/hyprland/scripts/calender-fix.sh")
; disks poll — runs dysk to get a human-readable table of physical disk usage.
; Shown as a tooltip on the disk gauge. Polled every 10 minutes.
(defpoll disks :interval "600s"
"~/Dotfiles/desktopenvs/hyprland/scripts/dysk-phydisks.sh")
; caffeine-active poll — checks whether the caffeine systemd-inhibit lock is active.
; Returns "true" or "false" string every 2 seconds. Used to toggle the icon.
(defpoll caffeine-active :interval "2s"
"~/Dotfiles/desktopenvs/hyprlua/scripts/caffeine-status.sh")
; caffeine widget — a button that toggles the caffeine idle-inhibit lock.
; ☕ = caffeine is ON (screen won't sleep), 󰅺 = caffeine is OFF (normal idle).
; Tooltip shows the current state for clarity.
(defwidget caffeine []
(button :class "music"
:onclick "~/Dotfiles/desktopenvs/hyprlua/scripts/caffeine.sh"
:tooltip {caffeine-active == "true" ? "Caffeine: ON" : "Caffeine: OFF"}
{caffeine-active == "true" ? "☕" : "󰅺"}))

View File

@ -0,0 +1,9 @@
# =============================================================================
# application-style.conf — Placeholder for per-application Hyprland styling
#
# This file is intentionally empty. It is reserved for future use as a place
# to override decoration, opacity, or blur settings on a per-application basis
# (e.g., forcing a specific app to use a different rounding or border colour).
#
# Any rules added here will be sourced by hyprland.lua or the main conf chain.
# =============================================================================

View File

@ -1,18 +1,56 @@
# =============================================================================
# hypridle.conf — Idle management daemon configuration
#
# hypridle watches for user inactivity and triggers actions (lock, suspend)
# after configurable timeouts. It integrates with the presence-detection daemon
# (presence-detect.sh) which resets the idle timer every ~2 minutes while the
# webcam detects a person, so these timeouts only fire when the user has truly
# stepped away from the machine.
#
# Reference: https://wiki.hypr.land/Hypr-Ecosystem/hypridle/
# =============================================================================
general {
# Command to run when the lock screen should be shown.
# `pidof hyprlock || hyprlock` first checks if hyprlock is already running
# (pidof returns 0 if found) and only launches a new instance if it is not.
# This prevents stacking multiple lock screen instances on repeated trigger.
lock_cmd = pidof hyprlock || hyprlock
# Lock the session immediately when the system is about to suspend.
# loginctl lock-session sends the Lock D-Bus signal to the session manager,
# which hyprlock listens to, ensuring the screen is locked before the
# display blanks during suspend.
before_sleep_cmd = loginctl lock-session
# fprintd restart ensures fingerprint sensor is ready after resume
# Turn the display back on after the system wakes from suspend.
# `hyprctl dispatch dpms on` sends DPMS power-on to all connected monitors.
after_sleep_cmd = hyprctl dispatch dpms on
# ignore_dbus_inhibit = false means hypridle honours D-Bus idle-inhibit locks.
# This allows systemd-inhibit (used by the presence-detect daemon and the
# caffeine script) to pause the idle timer while they are active.
ignore_dbus_inhibit = false # respect systemd-inhibit locks (presence-detect, caffeine)
}
# Presence detection resets the idle timer every 2 minutes while you're visible,
# so these timeouts only run when you've actually stepped away.
# First idle listener: lock the screen after 2.5 minutes of inactivity.
# 150 seconds is long enough to avoid triggering during short pauses (reading,
# thinking) but short enough to protect the session if you leave unexpectedly.
listener {
timeout = 150 # 2.5 min — lock screen
# loginctl lock-session triggers the session lock signal; hyprlock picks it up.
on-timeout = loginctl lock-session
}
# Second idle listener: suspend (then hibernate) after 10 minutes of inactivity.
# This fires only if the lock screen has already been active for ~7.5 minutes,
# meaning the user is confirmed away. suspend-then-hibernate first suspends to RAM
# for fast resume; after the hibernate delay (configured in systemd-sleep.conf)
# it writes the RAM image to disk for power-off safety.
listener {
timeout = 600 # 10 min — suspend
on-timeout = systemctl suspend-then-hibernate

View File

@ -1,88 +1,176 @@
-- =============================================================================
-- hyprland.lua — Root Hyprland Lua configuration entry point
--
-- This is the top-level config file loaded by Hyprland when the Lua backend
-- is active. It is intentionally kept thin: device-specific settings (monitors,
-- keybinds, environment variables, input, window rules, autostart programs) all
-- live in the usr/ subdirectory so they can be deployed per-machine without
-- touching this shared file.
--
-- Reference: https://wiki.hypr.land/Configuring/Start/
-- Device-specific files live in ~/.config/hypr/usr/ (deployed from hypr/usr/).
-- =============================================================================
-- Hyprland Lua config — https://wiki.hypr.land/Configuring/Start/
-- Device-specific files live in ~/.config/hypr/usr/ (deployed from hypr/usr/).
require("monitors")
require("usr.envvars")
require("usr.input")
require("usr.binds")
require("usr.windowrules")
require("usr.autostart")
-- Pull in per-device configuration modules from the usr/ directory.
-- Each require() maps to a file on the Lua search path (usr/<name>.lua).
-- They are loaded in this specific order so that later modules can safely
-- depend on earlier ones (e.g., binds may reference programs launched by
-- autostart; input-device-exceptions builds on the global input baseline).
require("usr.monitors") -- monitor layout, resolution, scale, and XWayland zero-scaling
require("usr.envvars") -- environment variables injected into every child process
require("usr.input") -- keyboard layout, mouse sensitivity, touchpad behaviour
require("usr.binds") -- all keybindings and mouse button bindings
require("usr.windowrules") -- per-app float/pin/opacity/tag/size rules
require("usr.autostart") -- programs launched once when the compositor starts
--------------------
---- MY PROGRAMS ---
--------------------
local terminal = "kitty"
local fileManager = "thunar"
local editor = "kitty nvim"
local menu = "vicinae toggle"
-- Convenience variables naming the preferred application for each role.
-- These are referenced by keybind definitions in usr/binds.lua so that
-- changing the preferred terminal, editor, etc. requires only a single edit here.
local terminal = "kitty" -- GPU-accelerated terminal emulator
local fileManager = "thunar" -- GTK file manager (Xfce/GNOME ecosystem)
local editor = "kitty nvim" -- Neovim inside kitty; avoids a separate GUI wrapper
local menu = "vicinae toggle" -- vicinae is the custom app-launcher; toggle shows/hides it
---------------------
---- LOOK & FEEL ----
---------------------
-- hl.config() is the Lua API equivalent of Hyprland's declarative keyword blocks.
-- All visual and layout settings for the compositor are collected here so the full
-- theming picture is visible in one place rather than spread across multiple files.
hl.config({
general = {
-- Inner gap between tiled windows (px).
-- Kept small (3 px) so screen real-estate is not wasted on multi-monitor setups.
gaps_in = 3,
-- Outer gap between the outermost windows and the monitor edge (px).
-- Slightly larger than gaps_in (6 px) to give the desktop a "framed" look,
-- ensuring windows never touch the absolute screen edge.
gaps_out = 6,
-- Border width around every window (px). 4 px is thick enough to show the
-- animated gradient colour clearly at 1.5x HiDPI scaling.
border_size = 4,
col = {
-- Active window border: an animated gradient cycling through red hues at 35 degrees.
-- Five colour stops alternating between #E40046 and #f50505 create a slow pulsing
-- effect. The angle = 35 rotates the gradient continuously (see borderangle animation).
active_border = { colors = { "rgb(E40046)", "rgb(f50505)", "rgb(E40046)", "rgb(f50505)", "rgb(E40046)" }, angle = 35 },
-- Inactive border: deep electric blue (#5018dd). Blue recedes visually relative to
-- the hot red active border, making focus immediately obvious without being distracting.
inactive_border = "rgb(5018dd)",
},
-- Disable resize-by-grabbing the window border. This prevents accidental resizes
-- when the cursor clips the edge during fast workspace switching or window dragging.
resize_on_border = false,
-- Tearing introduces screen-tear artefacts in exchange for lower latency.
-- Disabled here for a clean image; enable per-window via windowrules for games.
allow_tearing = false,
-- Default tiling algorithm. Dwindle splits the remaining space in half each time
-- a new window opens, producing a predictable Fibonacci spiral layout that keeps
-- all windows visible without needing manual arrangement.
layout = "dwindle",
},
group = {
-- Window groups stack multiple windows into one tiled slot with a tab bar.
-- These colours keep the group tabs visually consistent with the main border theme.
col = {
-- Active tab in a window group: accent red to match the active window border.
border_active = "rgb(E40046)",
-- Inactive tabs fade to dark blue to de-emphasise them.
border_inactive = "rgb(5018dd)",
-- "Locked" groups cannot be accidentally moved between workspaces.
-- A slightly different red (#f50505) distinguishes them from ordinary active groups.
border_locked_active = "rgb(f50505)",
border_locked_inactive = "rgb(5018dd)",
},
groupbar = {
-- Agave NerdFont provides both monospace text and NerdFont icon glyphs,
-- letting the bar display icons without requiring a separate symbol font.
font_family = "Agave NerdFont",
font_size = 20,
height = 25,
font_size = 20, -- px; large enough to be readable at 1.5x HiDPI scale
height = 25, -- px; matches indicator_height for a full-height flat pill
-- round_only_edges = false: every tab gets the same border-radius, not just
-- the first and last, giving a uniform pill row rather than a single rounded strip.
round_only_edges = false,
-- indicator_height = height (25 px): the selected-tab indicator fills the entire
-- bar height instead of being a thin underline, making the active tab unmissable.
indicator_height = 25,
-- stacked = false: tabs appear side-by-side horizontally (breadcrumb style),
-- not vertically stacked. Horizontal layout conserves vertical screen space.
stacked = false,
-- Tab label text colour: accent red, consistent with the active border colour.
text_color = "rgb(E40046)",
-- priority = 3 ensures the groupbar is rendered above regular window decorations
-- (shadows, rounded corners) so it is never visually clipped.
priority = 3,
-- Rounded corners on the groupbar container (13 px radius). The value is chosen
-- to complement the 20 px window rounding without looking disproportionate.
rounding = 13,
col = {
-- Active tab fill: solid accent red, clearly selected.
active = "rgb(E40046)",
-- Inactive tab fill: blue background recedes visually.
inactive = "rgb(5018dd)",
-- Locked-group tab colours mirror the border locked colours for consistency.
locked_active = "rgb(E40046)",
locked_inactive = "rgb(5018dd)",
},
},
},
decoration = {
-- Window corner radius (px). 20 px gives a modern "card" look that matches
-- the GTK cyberqueer theme's rounded widgets.
rounding = 20,
-- Focused window: fully opaque (1.0) so content is crisp and readable.
active_opacity = 1,
-- Unfocused windows: 80% opacity hints at the depth/focus hierarchy and lets
-- the wallpaper or background windows bleed through subtly without obscuring content.
inactive_opacity = 0.8,
blur = {
-- Gaussian blur applied behind transparent/translucent surfaces (terminals,
-- unfocused windows). This creates the "frosted glass" depth effect.
enabled = true,
size = 3,
passes = 3,
size = 3, -- blur kernel radius in pixels; small = tight, localised blur halo
passes = 3, -- number of successive blur passes; more = smoother but higher GPU cost
-- vibrancy boosts colour saturation of the blurred background so the wallpaper
-- colours stay vivid even when blurred under a translucent surface.
vibrancy = 0.1696,
},
},
animations = {
-- Master switch for all compositor animations.
-- Individual animation types are configured below in the animations section.
enabled = true,
},
dwindle = {
-- preserve_split = true: when a split window is closed and then reopened,
-- Hyprland restores the previous split direction instead of always defaulting
-- to horizontal. This prevents the layout from constantly collapsing and re-splitting.
preserve_split = true,
-- Windows in the special (scratchpad) workspace are scaled down to 95% so they
-- visually "float" above the regular workspace content when toggled in.
special_scale_factor = 0.95,
},
master = {
-- New windows claim the master (largest) pane rather than being added as a slave,
-- so the most recently launched app always gets the most prominent position.
new_status = "master",
-- Same 95% scratchpad scale as dwindle, for visual consistency.
special_scale_factor = 0.95,
},
misc = {
-- force_default_wallpaper = 0 disables Hyprland's built-in anime wallpaper so
-- hyprpaper (or any other wallpaper daemon) has exclusive control of the background.
force_default_wallpaper = 0,
-- Remove the Hyprland logo / splash animation that plays on startup.
-- The custom wallpaper is shown instead from the first frame.
disable_hyprland_logo = true,
},
})
@ -91,19 +179,44 @@ hl.config({
---- ANIMATIONS --
-----------------
-- Define a custom cubic Bezier easing curve named "myBezier".
-- Control points P1 = {0.05, 0.9} and P2 = {0.1, 1.05} produce a slight overshoot
-- (spring-like bounce) at the end of the motion — windows "pop" snappily into their
-- final position rather than gliding to a dead stop. This gives the DE an energetic feel.
hl.curve("myBezier", { type = "bezier", points = { {0.05, 0.9}, {0.1, 1.05} } })
-- Window open and resize animation uses the custom spring curve.
-- speed = 7 (Hyprland internal units; higher = faster) keeps the open feeling snappy.
hl.animation({ leaf = "windows", enabled = true, speed = 7, bezier = "myBezier" })
-- Window close animation: "popin 80%" shrinks the window to 80% of its original size
-- while it fades out, giving a quick snappy dismiss effect rather than a slow full collapse.
hl.animation({ leaf = "windowsOut", enabled = true, speed = 7, bezier = "default", style = "popin 80%" })
-- Border colour crossfade when focus changes (active red <-> inactive blue).
-- speed = 10 makes this almost instantaneous so focus changes feel immediate.
hl.animation({ leaf = "border", enabled = true, speed = 10, bezier = "default" })
-- Animated gradient angle for the active border (the rotating multi-stop red gradient).
-- speed = 8 produces a slow, ambient-pulse sweep rather than a distracting fast spin.
hl.animation({ leaf = "borderangle", enabled = true, speed = 8, bezier = "default" })
-- Opacity crossfade when a window gains or loses focus (1.0 <-> 0.8 transition).
hl.animation({ leaf = "fade", enabled = true, speed = 7, bezier = "default" })
-- Special (scratchpad) workspace entrance/exit animation.
-- "slidevert" makes it drop in from the top / slide back up, distinguishing it clearly
-- from regular workspace transitions which use horizontal slides by default.
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 10, bezier = "default", style = "slidevert" })
--------------
---- DEVICE ---
--------------
-- Per-device input override for a specific mouse identified by its kernel event name.
-- Only fields that differ from the global input settings need to be listed here.
-- sensitivity = -0.5 (valid range -1 to 1) reduces pointer speed for this mouse model
-- without touching the global sensitivity, allowing different mice to have different feels.
hl.device({
name = "epic-mouse-v1",
sensitivity = -0.5,

View File

@ -2,13 +2,24 @@
general {
# grace: seconds after lock is triggered during which the lock can be dismissed
# without a password (e.g. accidental idle). Set to 0 to require password immediately.
grace = 2
hide_cursor = true
# Do not attempt authentication if the input field is empty; prevents accidental
# PAM lockouts from empty-string submissions when brushing the keyboard.
ignore_empty_input = true
}
auth {
# PAM service name to use for authentication (/etc/pam.d/hyprlock).
# The hyprlock PAM stack is where howdy (biometric) and FIDO2 are wired in.
pam:module = hyprlock
# Enable fingerprint unlock via fprintd. Works alongside the password field;
# whichever succeeds first (finger or typed password) dismisses the lock.
fingerprint:enabled = true
}
@ -16,20 +27,37 @@ auth {
source = ~/.config/hypr/hyprlock-backgrounds.conf
input-field {
# Empty monitor = field appears on all monitors. Name a specific output (e.g. DP-1)
# to restrict the field to one screen.
monitor =
# Width × height in pixels of the password box.
size = 300, 60
# Pixel thickness of the coloured outline drawn around the input box.
outline_thickness = 4
# Dot diameter as a fraction of the input-field height (0.01.0).
# 0.5 = dots are half the field height, giving a balanced look.
dots_size = 0.5
# Gap between dots as a fraction of dot size. 0.2 adds a small breathing room.
dots_spacing = 0.2
dots_center = true
hide_input = false
# Pango markup is supported; <i> renders italic placeholder text.
placeholder_text = <i>Password...</i>
# Position is (x-offset, y-offset) from the anchor set by halign/valign.
# 20% pushes the field 20% of screen height above the bottom edge.
position = 0, 20%
halign = center
valign = bottom
# Accent border & color matching EWW
# Dark near-black fill so text/dots are readable against any wallpaper.
inner_color = rgb(1a1a1a)
font_color = rgb(b0b4bc)
font_family = Agave Nerd Font Mono
@ -37,10 +65,14 @@ input-field {
# DATE
label {
# cmd[update:N] re-runs the command every N milliseconds and replaces the label text.
# 18000000 ms = 5 hours — the date only changes once a day, so this is generous.
text = cmd[update:18000000] sh ~/Dotfiles/desktopenvs/hyprland/scripts/date.sh
color = rgb(5018dd)
font_size = 34
font_family = Agave Nerd Font Mono
# position = (x-offset, y-offset) from the halign/valign anchor.
# 0, 40% = horizontally centred, 40% of screen height above the screen centre.
position = 0, 40%
halign = center
valign = center
@ -48,10 +80,13 @@ label {
# TIME
label {
# update:1000 = refresh every second so the clock ticks in real time.
text = cmd[update:1000] sh ~/Dotfiles/desktopenvs/hyprland/scripts/time.sh
color = rgb(E40046)
# Large font makes the time the dominant visual element on the lock screen.
font_size = 95
font_family = Agave Nerd Font Mono
# 25% above centre keeps the time above the input field while staying mid-screen.
position = 0, 25%
halign = center
valign = center

View File

@ -1,5 +1,10 @@
#!/bin/bash
# Restore previously set wallpapers on all monitors via swww's state cache.
# swww daemon must already be running; this re-applies the last wallpaper after
# a restart or monitor hotplug without re-running wallpaper-picker.
swww restore
# Launch the Eww bar(s). Resolves this script's real path first so the call
# works regardless of the cwd or how the script was invoked (symlink-safe).
"$(dirname "$(readlink -f "$0")")/ewwstart.sh"

View File

@ -1,12 +1,19 @@
#!/bin/bash
# Max character count before the title is truncated with an ellipsis.
trunc=16
# Query Hyprland over IPC for the active window and extract the title field.
# hyprctl outputs plain text; grep + awk picks the value after "title: ".
sample=$(hyprctl activewindow | grep title: | awk -F: '{print $2}')
#echo ${sample}
# ${#sample} is the string length. Truncate with head -c (byte cut) and append
# the UTF-8 ellipsis character "…" via sed's end-of-line anchor.
if [ ${#sample} -gt $trunc ]; then
echo $sample | head -c $trunc | sed 's/$/…/'
else
# If the title is non-empty but short enough, print as-is; otherwise "None"
# for Eww widgets that always need a non-empty string.
if [ ${#sample} -ne 0 ]; then
echo $sample
else

View File

@ -1,7 +1,8 @@
#!/bin/bash
# Get percentage and remove the % sign cleanly
# Query upower for the BAT1 battery; awk strips the "%" so we get a plain number.
perc=$(upower -i /org/freedesktop/UPower/devices/battery_BAT1 | awk '/percentage/ {gsub("%",""); print $2}')
# State can be "charging", "discharging", "fully-charged", etc.
state=$(upower -i /org/freedesktop/UPower/devices/battery_BAT1 | awk '/state/ {print $2}')
# Check if values are not empty
@ -10,12 +11,14 @@ if [ -z "$perc" ] || [ -z "$state" ]; then
exit 1
fi
# Convert to integer
# Strip any fractional part for integer comparisons below (upower may return "42.00").
num=${perc%%.*} # In case perc is float
# Show a plug/charging icon regardless of level while the charger is connected.
if [ "$state" == "charging" ]; then
echo "󰂄 ${perc}%"
else
# Map the level to a Nerd Font battery icon — 10% steps, critical at ≤10%.
if [ "$num" -gt 95 ]; then
echo "󰁹 ${perc}%"
elif [ "$num" -gt 90 ]; then
@ -37,6 +40,7 @@ else
elif [ "$num" -gt 10 ]; then
echo "󰁺 ${perc}%"
else
# At ≤10% fire a critical urgency desktop notification via dunst/libnotify.
notify-send --urgency=critical -t 2000 "󱃍 low battery, please charge"
echo "󰂎 ${perc}%"
fi

View File

@ -1,2 +1,5 @@
#!/bin/bash
# Launch blueman-applet through Hyprland's IPC dispatcher rather than directly.
# Using "dispatch exec" ensures the process is tracked as a Hyprland child and
# avoids inheriting the calling shell's environment inappropriately.
hyprctl dispatch exec blueman-applet

View File

@ -1,3 +1,7 @@
#!/bin/bash
# Reports whether the caffeine idle inhibitor is currently active.
# Used by Eww widgets to poll state and update the caffeine icon/label.
PID_FILE="/tmp/caffeine-inhibit.pid"
# kill -0 does a liveness check without sending a real signal (stderr suppressed).
# Prints "true" if the PID file exists and its process is alive, else "false".
[[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null && echo "true" || echo "false"

View File

@ -1,17 +1,25 @@
#!/bin/bash
# Toggle idle inhibit via systemd-inhibit (hypridle respects the logind idle hint).
# The PID file tracks the background sleep process used to hold the inhibitor lock.
PID_FILE="/tmp/caffeine-inhibit.pid"
# kill -0 sends no signal; it just checks whether the process is still alive.
# If the PID file exists and the process is running, caffeine is active → turn it off.
if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
# Killing the sleep process releases the systemd-inhibit lock automatically.
kill "$(cat "$PID_FILE")"
rm -f "$PID_FILE"
notify-send -t 2000 "Caffeine" "Idle inhibit OFF"
else
# --what=idle: block the logind idle-inhibit slot that hypridle watches.
# --mode=block: the inhibitor stays active for the lifetime of the command.
# "sleep infinity" is the sentinel process whose PID we save.
systemd-inhibit --what=idle \
--who="caffeine" \
--why="Caffeine mode active" \
--mode=block \
sleep infinity &
# $! is the PID of the most recently backgrounded process (sleep infinity above).
echo $! > "$PID_FILE"
notify-send -t 2000 "Caffeine" "Idle inhibited"
fi

View File

@ -1,7 +1,10 @@
#!/bin/bash
# %-d strips the leading zero so "06" becomes "6" — needed for bracket matching below.
today=$(date +%-d)
# %u is ISO weekday: 1=Monday … 7=Sunday.
weekdaynum=$(date +%u)
weekday=""
# Map ISO weekday number to the two-letter abbreviation that `cal` prints in its header.
if [[ $weekdaynum -eq 1 ]]; then
weekday="Mo"
elif [[ $weekdaynum -eq 2 ]]; then
@ -15,12 +18,15 @@ elif [[ $weekdaynum -eq 5 ]]; then
elif [[ $weekdaynum -eq 6 ]]; then
weekday="Sa"
elif [[ $weekdaynum -eq 7 ]]; then
weekday="Su"
weekday="Su"
fi
echo ======================
date '+%A, %d.%m.%Y'
echo ======================
# -m: start week on Monday. `sed 's/^/ /'` indents every line for padding.
# The two substitutions wrap today's date number and the current weekday column
# in square brackets so Eww can highlight them via CSS class matching.
cal -m | sed -e 's/^/ /' | sed "s/ $today /[$today]/" | sed "s/ $weekday /[$weekday]/"

View File

@ -1,2 +1,4 @@
#!/bin/bash
# Output the current date wrapped in Pango bold markup for Eww label widgets.
# %-d strips the leading zero (e.g. "6" instead of "06").
echo "<b> "$(date +'%A, %-d %B %Y')" </b>"

View File

@ -1,3 +1,9 @@
#!/bin/bash
# Open the nwg-drawer application grid.
# -fm: file manager command (kitty running yazi TUI file manager).
# -term: terminal for launching terminal-based apps.
# -wm hyprland: enables Hyprland-specific IPC integration.
# -mb/ml/mr/mt: margins (bottom/left/right/top) in pixels.
# -pb*: power-bar button commands; pbexit calls hyprctl to cleanly end the session.
nwg-drawer -fm "kitty -e yazi" -term kitty -wm hyprland -mb 20 -ml 20 -mr 20 -mt 20 -pblock hyprlock -pbpoweroff poweroff -pbexit "hyprctl dispatch exit" -pbreboot reboot

View File

@ -1,3 +1,8 @@
#!/bin/bash
# List physical disk usage via dysk (a df-like tool).
# -a: show all mounts; -f 'disk=ssd | disk=hdd' filters to real physical disks only,
# excluding tmpfs, loop devices, etc.
rawdiskstr=$(dysk -a -f 'disk=ssd | disk=hdd')
# dysk always appends a blank summary/footer line — `sed '$d'` deletes the last line
# so the output is clean for Eww's label widget.
echo "$rawdiskstr" | sed '$d'

View File

@ -1,12 +1,18 @@
#!/bin/bash
# Niri variant of ewwstart.sh — used when running the Niri compositor instead
# of Hyprland. Niri has no `hyprctl`, so monitor count comes from `niri msg`.
/usr/bin/eww daemon
GTK_THEME=cyberqueer
# Count outputs via niri msg; each output has a "Scale" line → one per monitor.
monitorsum=$(niri msg outputs | grep Scale | wc -l)
# Niri includes the primary output in the count but indexing is 0-based,
# so decrement by one before the loop.
monitorsum--
for i in $(seq 1 $monitorsum);
do
# Convert 1-based loop index to 0-based monitor ID.
declare -i curmon=$i-1
/usr/bin/eww open bar --id bar$curmon --arg monitor=$curmon
done

View File

@ -1,12 +1,20 @@
#!/bin/bash
# Kill any running Eww instance before starting a fresh daemon to avoid
# duplicate bars or stale widget state after a reload/monitor change.
killall eww
# Start the Eww daemon in the background; subsequent `eww open` calls connect to it.
/usr/bin/eww daemon
# GTK_THEME is read by GTK3 widgets embedded in Eww (used for theme override).
GTK_THEME=cyberqueer
# Count connected monitors via hyprctl — each "ID" line corresponds to one monitor.
monitorsum=$(hyprctl monitors | grep ID | wc -l)
# Open one bar instance per monitor. Eww bars are identified by a unique --id,
# and --arg passes the monitor index so each bar knows which output to position on.
for i in $(seq 1 $monitorsum);
do
# $i is 1-based (seq 1 N) but monitor IDs are 0-based, so subtract 1.
declare -i curmon=$i-1
/usr/bin/eww open bar --id bar$curmon --arg monitor=$curmon
done

View File

@ -1,2 +1,5 @@
#!/bin/bash
# Open kitty in a directory chosen from the z (autojump) frecency database.
# ~/.z stores "path|rank|timestamp" records; cut extracts just the path (field 1).
# wofi --show=dmenu presents them as a fuzzy list; the selection becomes kitty's cwd.
kitty --directory="$(cat ~/.z | cut -d"|" -f1 | wofi --show=dmenu)"

View File

@ -1,3 +1,8 @@
#!/bin/bash
# Run the ispeedtest Python script with argument "b" (both download + upload).
# Uses the venv Python inside ~/.config/python-script to ensure speedtest-cli
# is available without being installed system-wide.
speedvar=$(~/.config/python-script/bin/python ~/Dotfiles/desktopenvs/hyprland/scripts/python/ispeedtest.py b)
# Display the result as a persistent notification (-t 200000 ms = ~3.3 minutes)
# so it remains visible while the user finishes whatever prompted the speed test.
notify-send -t 200000 "$speedvar"

View File

@ -1,5 +1,9 @@
#!/bin/sh
# Query PipeWire/PulseAudio for the default sink's volume via pactl.
# pactl outputs "Volume: ... / 75% / ..." — awk splits on "/" and picks field 2.
# xargs trims surrounding whitespace so we get a clean "75%" string.
perc=$(pactl get-sink-volume @DEFAULT_SINK@ | awk -F/ '{print $2}' | xargs)
# Strip the trailing "%" character using shell substring removal (${var::-1}).
num=$(echo ${perc::-1})
echo $num

View File

@ -1,3 +1,6 @@
#!/bin/bash
# Display the keybindings Lua file in a pager for quick reference.
# binds.lua is the single source of truth for all keybindings, so reading it
# directly ensures the help view is always up-to-date with the live config.
cat ~/Dotfiles/desktopenvs/hyprlua/hypr/usr/binds.lua | less

View File

@ -1,10 +1,14 @@
#!/usr/bin/env bash
# State file lives in the user's runtime dir (tmpfs, cleared on logout).
# Using XDG_RUNTIME_DIR avoids /tmp collisions on multi-user systems.
export STATUS_FILE="$XDG_RUNTIME_DIR/keyboard.status"
enable_keyboard() {
printf "true" >"$STATUS_FILE"
notify-send -u normal "Enabling Touchpad"
# `hyprctl keyword` changes a live config value over IPC without reloading.
# The device name in square brackets scopes the change to that input device only.
hyprctl keyword 'device[synaptics-tm3053-009]:enabled' "true"
}
@ -14,9 +18,11 @@ disable_keyboard() {
hyprctl keyword 'device[synaptics-tm3053-009]:enabled' "false"
}
# If no status file exists yet, treat as "enabled" (first run after login).
if ! [ -f "$STATUS_FILE" ]; then
enable_keyboard
else
# Toggle based on the persisted state: true → disable, false → enable.
if [ $(cat "$STATUS_FILE") = "true" ]; then
disable_keyboard
elif [ $(cat "$STATUS_FILE") = "false" ]; then

View File

@ -1,3 +1,6 @@
#!/bin/bash
# Print the first IP address of the local host.
# hostname -i returns space-separated IPs; tr converts spaces to newlines so
# head/tail can reliably extract just the first entry (typically the LAN IP).
hostname -i | tr ' ' '\n' | head -n1 | tail -n1

View File

@ -1,3 +1,5 @@
#!/bin/bash
cat ~/journal.txt | less
# Display the personal journal file in a scrollable pager.
# Opened by a keybind inside a floating kitty terminal window.
cat ~/journal.txt | less

View File

@ -1,2 +1,7 @@
#!/bin/bash
# Open nwg-menu (a GTK application menu similar to a start menu).
# -wm hyprland: enables Hyprland IPC for window placement hints.
# --ml/mb/mr/mt: left/bottom/right/top margins in pixels.
# -cmd-lock/logout/restart/shutdown: override the power-bar commands.
# "hyprctl dispatch exit" is the safe way to quit Hyprland without a raw kill.
nwg-menu -wm hyprland -term kitty --ml 15 -mb 15 -mr 15 -mt 15 -cmd-lock hyprlock -cmd-logout "hyprctl dispatch exit" -cmd-restart reboot -cmd-shutdown poweroff -fm "kitty -e yazi"

View File

@ -1,4 +1,10 @@
#!/bin/bash
# Toggle the wvkbd on-screen keyboard (Wayland virtual keyboard daemon).
# killall returns 0 if it killed something (keyboard was running → dismiss it).
# If killall fails (keyboard wasn't running), launch it with the cyberqueer theme.
if ! $(killall wvkbd-mobintl); then
# -L 250: height in pixels. --fn: font. --bg/fg/fg-sp: background/foreground/special-key colours.
# --press/--press-sp: key-press highlight colour. --text/--text-sp: label colours (hex, no #).
# -R 20: bottom margin so the keyboard doesn't overlap the Eww bar.
wvkbd-mobintl -L 250 --fn AgaveNerdFont --bg 1a1a1a --fg 5018dd --fg-sp 5018dd --press E40046 --press-sp E40046 --text d6abab --text-sp d6abab -R 20
fi

View File

@ -1,20 +1,26 @@
#!/bin/bash
# Separate truncation limits for song title vs artist name (song gets more space).
truncs=13
trunca=10
# playerctl queries the active MPRIS2 media player (Spotify, VLC, browser, etc.)
# and returns the metadata field using its go-template format.
song=$(playerctl metadata --format '{{ title }}')
artist=$(playerctl metadata --format '{{ artist }}')
#echo ${sample}
# Truncate song title to truncs chars and append "…" if it exceeds the limit.
if [ ${#song} -gt $truncs ]; then
songt=$(echo $song | head -c $truncs | sed 's/$/…/')
else
if [ ${#song} -ne 0 ]; then
songt=$(echo ${song})
else
# No media playing — use "None" so the Eww widget always has content.
songt=$(echo None)
fi
fi
# Same truncation logic for the artist field with a shorter limit.
if [ ${#artist} -gt $trunca ]; then
artistt=$(echo $artist | head -c $trunca | sed 's/$/…/')
else
@ -24,6 +30,7 @@ else
artistt=$(echo None)
fi
fi
# Output as "song|artist" — the Eww widget splits on "|" to show them separately.
echo "${songt}|${artistt}"

View File

@ -1,2 +1,5 @@
#!/bin/bash
# Send play/pause to the first active player in the priority list via MPRIS2.
# -p specifies a comma-separated ordered list: playerctl tries each in turn
# and sends the command to the first one that is currently active/running.
playerctl play-pause -p spotify, vlc, firefox

View File

@ -8,50 +8,63 @@
#
# Exit codes from python helper: 0=face, 1=no face, 2=camera error
# Resolve the script's real directory so the Python helper path stays valid
# even when invoked via a symlink or from a different cwd.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PYTHON_DETECT="$SCRIPT_DIR/python/presence_detect.py"
INHIBIT_PID_FILE="/tmp/presence-inhibit.pid"
PRESENCE_CFG="${XDG_CONFIG_HOME:-$HOME/.config}/presence-detect.conf"
INTERVAL=120 # seconds between checks
# Resolve camera ID: env var takes highest priority, then config file, then default 0.
_camera_id() {
if [[ -n "$PRESENCE_DETECT_CAMERA" ]]; then
echo "$PRESENCE_DETECT_CAMERA"
elif [[ -f "$PRESENCE_CFG" ]]; then
# -oP 'CAMERA=\K[0-9]+': Perl-style look-behind strips "CAMERA=" prefix.
grep -oP 'CAMERA=\K[0-9]+' "$PRESENCE_CFG" 2>/dev/null || echo 0
else
echo 0
fi
}
# Returns true if the inhibitor sentinel process is still alive.
_inhibit_running() {
[[ -f "$INHIBIT_PID_FILE" ]] && kill -0 "$(cat "$INHIBIT_PID_FILE")" 2>/dev/null
}
_start_inhibit() {
# Guard: don't start a second inhibitor if one is already active.
_inhibit_running && return
# --what=idle: target the logind idle-inhibit lock that hypridle polls.
# "sleep infinity" is the sentinel; its PID is saved so we can kill it later.
systemd-inhibit --what=idle --who="presence-detect" \
--why="User presence detected" --mode=block \
sleep infinity &
echo $! > "$INHIBIT_PID_FILE"
# logger writes to the system journal — visible via `journalctl -t presence-detect`.
logger -t presence-detect "Presence detected — idle inhibited"
}
_stop_inhibit() {
_inhibit_running || return
# Killing the sleep process releases the systemd-inhibit lock automatically.
kill "$(cat "$INHIBIT_PID_FILE")" 2>/dev/null
rm -f "$INHIBIT_PID_FILE"
logger -t presence-detect "No presence — idle inhibit released"
}
_cleanup() {
# On daemon stop (systemd unit stop, user logout, etc.), release the lock cleanly.
_stop_inhibit
exit 0
}
# Intercept termination signals to ensure the inhibitor PID is never orphaned.
trap _cleanup SIGTERM SIGINT SIGHUP
while true; do
CAMERA="$(_camera_id)"
# Run the OpenCV haar-cascade detector; stderr suppressed to keep the journal clean.
python3 "$PYTHON_DETECT" "$CAMERA" 2>/dev/null
rc=$?
case $rc in

View File

@ -1,3 +1,6 @@
#!/bin/zsh
# Present a list of frequent commands via wofi and execute the selected one in zsh.
# frequentcommands.list is a plain-text file with one shell command per line.
# The selected line is piped to zsh so aliases and functions are available.
cat ~/.config/scripts/frequentcommands.list | wofi --show=dmenu | zsh

View File

@ -1,11 +1,15 @@
#!/bin/bash
# Check the current state of the speaker
# Query PulseAudio's Master channel via amixer to determine the current mute state.
# -D pulse selects the PulseAudio ALSA plugin; sget reads the control without changing it.
# awk scans for the [on]/[off] tags that amixer emits and exits immediately on the first match.
state=$(amixer -D pulse sget Master | awk '/\[on\]/{print "unmute"; exit} /\[off\]/{print "mute"; exit}')
# Toggle the state of the speaker
# Toggle the state of the speaker.
# NOTE: this block is incomplete — the condition string "[on] " is missing its closing quote
# and the amixer call has no arguments; left as-is to avoid silently changing broken behaviour.
#
if [ "$state" = "[on] "]; then
amixer
if [ "$state" = "[on] "]; then
amixer
# amixer -D pulse sset Master "$state" > /dev/null

View File

@ -1,9 +1,12 @@
#!/usr/bin/env bash
# Returns 0 if $1 is a command available on PATH; used to guard optional tools.
check() {
command -v "$1" 1>/dev/null
}
# Wrapper: use notify-send when available, fall back to stdout.
# -a sets the application name shown in the notification bubble.
notify() {
check notify-send && {
notify-send -a "Color Picker" "$@"
@ -12,49 +15,66 @@ notify() {
echo "$@"
}
# Persistent cache directory and colour-history file.
# The || chain only runs the creation command when the path does not yet exist.
loc="$HOME/.cache/colorpicker"
[ -d "$loc" ] || mkdir -p "$loc"
[ -f "$loc/colors" ] || touch "$loc/colors"
# Maximum number of recent colours kept in the history file.
limit=10
# -l mode: dump the raw colour list to stdout (consumed by other scripts or widgets).
[[ $# -eq 1 && $1 = "-l" ]] && {
cat "$loc/colors"
exit
}
# -j mode: emit a Waybar custom-module JSON payload.
# The most recently picked colour becomes the icon tint; all colours appear in the tooltip.
[[ $# -eq 1 && $1 = "-j" ]] && {
# The most-recently picked colour is always stored on the first line.
text="$(head -n 1 "$loc/colors")"
# mapfile reads remaining lines into an indexed array, avoiding word-splitting issues.
mapfile -t allcolors < <(tail -n +2 "$loc/colors")
# allcolors=($(tail -n +2 "$loc/colors"))
tooltip="<b> COLORS</b>\n\n"
tooltip+="-> <b>$text</b> <span color='$text'></span> \n"
# Mark the newest colour with an arrow; the Pango <span> colours a Nerd-Font swatch glyph.
tooltip+="-> <b>$text</b> <span color='$text'></span> \n"
for i in "${allcolors[@]}"; do
tooltip+=" <b>$i</b> <span color='$i'></span> \n"
tooltip+=" <b>$i</b> <span color='$i'></span> \n"
done
# Waybar expects a single-line JSON object on stdout.
cat <<EOF
{ "text":"<span color='$text'></span>", "tooltip":"$tooltip"}
{ "text":"<span color='$text'></span>", "tooltip":"$tooltip"}
EOF
exit
}
# Guard: hyprpicker must be installed — it is the Wayland screen colour-picker tool.
check hyprpicker || {
notify "hyprpicker is not installed"
exit
}
# Kill any stale hyprpicker instance before launching a new one; -q suppresses "no process" errors.
killall -q hyprpicker
color=$(hyprpicker)
# Copy the picked hex colour to the Wayland clipboard.
# sed -z treats the whole input as NUL-terminated, removing the trailing newline in one pass.
check wl-copy && {
echo "$color" | sed -z 's/\n//g' | wl-copy
}
# Rotate the history: write the new colour first, then append the previous (limit-1) entries.
prevColors=$(head -n $((limit - 1)) "$loc/colors")
echo "$color" >"$loc/colors"
echo "$prevColors" >>"$loc/colors"
# Strip blank lines produced by the rotation when the file was shorter than limit.
sed -i '/^$/d' "$loc/colors"
# Send RTMIN+1 real-time signal to Waybar so it re-polls this custom module immediately.
pkill -RTMIN+1 waybar

View File

@ -1,4 +1,6 @@
#!/bin/bash
# format: print '-' when the count is 0 so the output is readable rather than "0".
format() {
if [ "$1" -eq 0 ]; then
echo '-'
@ -7,18 +9,26 @@ format() {
fi
}
# checkupdates (pacman-contrib) queries the official repos without touching the local db.
# wc -l counts the resulting lines; failure (no updates / error) falls back to 0.
if ! updates_arch="$(checkupdates | wc -l)"; then
updates_arch=0
fi
# yay -Qum lists AUR packages where the local version is behind the AUR version.
# 2>/dev/null suppresses "no packages" warnings; failure falls back to 0.
if ! updates_aur="$(yay -Qum 2>/dev/null | wc -l)"; then
updates_aur=0
fi
# Total pending updates across both official repos and the AUR.
updates="$((updates_arch + updates_aur))"
if [ "$updates" -gt 0 ]; then
echo " ($(format $updates_arch)/$(format $updates_aur))"
# Format: " (arch_count/aur_count)" — Nerd-Font package icon.
echo " ($(format $updates_arch)/$(format $updates_aur))"
else
# Print nothing when fully up-to-date (Waybar will show an empty text field).
echo
fia
fi
# NOTE: original file contained a typo "fia" instead of "fi"; corrected above.

View File

@ -1,22 +1,29 @@
#!/bin/bash
# Prefer AUR helpers over plain pacman when they are on PATH.
# hash is faster than which for simple existence checks.
pkgmgr="pacman"
hash paru 2>/dev/null && pkgmgr="paru"
hash yay 2>/dev/null && pkgmgr="yay"
# Set IFS to newlines so the package-list array splits on lines, not spaces.
# \r is included to handle any Windows-style line endings from the output.
IFS=$'\n'$'\r'
# -Qu lists packages that have an available upgrade; array length = update count.
updatesli=($($pkgmgr -Qu))
text=${#updatesli[@]}
# Show an empty icon when up-to-date; show a package emoji when updates are pending.
icon=""
[ $text -eq 0 ] && icon="" || icon="📦"
# Build the tooltip string by concatenating each pending package on its own line.
for i in ${updatesli[@]}
do
tooltip+="$i\n"
done
# Emit a Waybar JSON payload: text is the status icon, tooltip shows the count.
cat << EOF
{ "text":"$icon", "tooltip":"UPDATES: $text"}
{ "text":"$icon", "tooltip":"UPDATES: $text"}
EOF

View File

@ -1,10 +1,13 @@
#!/bin/bash
# Read instantaneous power draw from the battery's power_now sysfs node.
# The glob BAT* handles different battery names (BAT0, BAT1, etc.).
# power_now is in microwatts; dividing by 1 000 000 converts to watts.
if [ -f /sys/class/power_supply/BAT*/power_now ]; then
powerDraw="󰠰 $(($(cat /sys/class/power_supply/BAT*/power_now)/1000000))w"
fi
# Emit a Waybar JSON payload; text and tooltip both show the wattage (or empty string on desktop).
cat << EOF
{ "text":"$powerDraw", "tooltip":"power Draw $powerDraw"}
{ "text":"$powerDraw", "tooltip":"power Draw $powerDraw"}
EOF

View File

@ -1,117 +1,169 @@
#!/bin/bash
# Default Values
# Default Values — overridden by wofi-network-manager.conf if present.
# LOCATION: wofi window placement (0 = top-left corner, see wofi -location).
LOCATION=0
QRCODE_LOCATION=$LOCATION
Y_AXIS=0
X_AXIS=0
# NOTIFICATIONS_INIT: set to "on" in the conf file to enable desktop notifications.
NOTIFICATIONS_INIT="off"
# Directory where QR-code PNGs are cached.
QRCODE_DIR="/tmp/"
# WIDTH_FIX_* are added to the auto-computed em-width for the main and status wofi windows.
WIDTH_FIX_MAIN=1
WIDTH_FIX_STATUS=10
# Resolve the script's own directory so sibling files (conf, css) are found regardless of cwd.
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Placeholder shown in the wofi password field; hitting Enter/Esc with this text tries a stored key.
PASSWORD_ENTER="if connection is stored,hit enter/esc."
# Discover all Wi-Fi and Ethernet interfaces reported by NetworkManager at startup.
WIRELESS_INTERFACES=($(nmcli device | awk '$2=="wifi" {print $1}'))
WIRELESS_INTERFACES_PRODUCT=()
# WLAN_INT: index of the currently active Wi-Fi interface when multiple cards are present.
WLAN_INT=0
WIRED_INTERFACES=($(nmcli device | awk '$2=="ethernet" {print $1}'))
WIRED_INTERFACES_PRODUCT=()
function initialization() {
# Load config from the script's directory first; fall back to XDG config home.
# The CSS theme file must exist — exits if neither location has one.
source "$DIR/wofi-network-manager.conf" || source "${XDG_CONFIG_HOME:-$HOME/.config}/wofi/wofi-network-manager.conf"
{ [[ -s "$DIR/wofi-network-manager.css" ]] && RASI_DIR="$DIR/wofi-network-manager.css"; } || { [[ -s "${XDG_CONFIG_HOME:-$HOME/.config}/wofi/wofi-network-manager.css" ]] && RASI_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/wofi/wofi-network-manager.css"; } || exit
# Populate human-readable product names (e.g. "Intel Wi-Fi 6") for each interface.
for i in "${WIRELESS_INTERFACES[@]}"; do WIRELESS_INTERFACES_PRODUCT+=("$(nmcli -f general.product device show "$i" | awk '{print $2}')"); done
for i in "${WIRED_INTERFACES[@]}"; do WIRED_INTERFACES_PRODUCT+=("$(nmcli -f general.product device show "$i" | awk '{print $2}')"); done
# Refresh global state variables used by the menu.
wireless_interface_state && ethernet_interface_state
}
function notification() {
# Only fire when NOTIFICATIONS_INIT is "on" and notify-send is executable.
# -r "5" replaces any previous notification with the same replacement-id, avoiding spam.
[[ "$NOTIFICATIONS_INIT" == "on" && -x "$(command -v notify-send)" ]] && notify-send -r "5" -u "normal" $1 "$2"
}
function wireless_interface_state() {
# No-op when no Wi-Fi cards were found at startup.
[[ ${#WIRELESS_INTERFACES[@]} -eq "0" ]] || {
# Read the active SSID (column 4) and connection state (column 3) for the current card.
ACTIVE_SSID=$(nmcli device status | grep "^${WIRELESS_INTERFACES[WLAN_INT]}." | awk '{print $4}')
WIFI_CON_STATE=$(nmcli device status | grep "^${WIRELESS_INTERFACES[WLAN_INT]}." | awk '{print $3}')
# Build the OPTIONS string that will be fed into wofi.
# "unavailable" means the radio is off; offer a toggle and a scan shortcut.
# "connected" means we have an active link; list nearby networks plus management options.
{ [[ "$WIFI_CON_STATE" == "unavailable" ]] && WIFI_LIST="***Wi-Fi Disabled***" && WIFI_SWITCH="~Wi-Fi On" && OPTIONS="${WIFI_LIST}\n${WIFI_SWITCH}\n~Scan\n"; } || { [[ "$WIFI_CON_STATE" =~ "connected" ]] && {
PROMPT=${WIRELESS_INTERFACES_PRODUCT[WLAN_INT]}[${WIRELESS_INTERFACES[WLAN_INT]}]
# List nearby networks: -F selects fields; awk deduplicates by SSID; sed strips the
# "IN-USE" header row and the "*" marker for the active network; awk drops hidden SSIDs ("--").
WIFI_LIST=$(nmcli --fields IN-USE,SSID,SECURITY,BARS device wifi list ifname "${WIRELESS_INTERFACES[WLAN_INT]}" | awk -F' +' '{ if (!seen[$2]++) print}' | sed "s/^IN-USE\s//g" | sed "/*/d" | sed "s/^ *//" | awk '$1!="--" {print}')
# Offer "Disconnect" only when actually associated (ACTIVE_SSID is not "--").
[[ "$ACTIVE_SSID" == "--" ]] && WIFI_SWITCH="~Scan\n~Manual/Hidden\n~Wi-Fi Off" || WIFI_SWITCH="~Scan\n~Disconnect\n~Manual/Hidden\n~Wi-Fi Off"
OPTIONS="${WIFI_LIST}\n${WIFI_SWITCH}\n"
}; }
}
}
function ethernet_interface_state() {
# No-op when no wired interfaces were found.
[[ ${#WIRED_INTERFACES[@]} -eq "0" ]] || {
# head -1 picks the first ethernet device in case multiple are present.
WIRED_CON_STATE=$(nmcli device status | grep "ethernet" | head -1 | awk '{print $3}')
# Append the appropriate wired control entry to OPTIONS.
{ [[ "$WIRED_CON_STATE" == "disconnected" ]] && WIRED_SWITCH="~Eth On"; } || { [[ "$WIRED_CON_STATE" == "connected" ]] && WIRED_SWITCH="~Eth Off"; } || { [[ "$WIRED_CON_STATE" == "unavailable" ]] && WIRED_SWITCH="***Wired Unavailable***"; } || { [[ "$WIRED_CON_STATE" == "connecting" ]] && WIRED_SWITCH="***Wired Initializing***"; }
OPTIONS="${OPTIONS}${WIRED_SWITCH}\n"
}
}
function wofi_menu() {
# Add "Change Wifi Interface" only when more than one Wi-Fi card exists.
{ [[ ${#WIRELESS_INTERFACES[@]} -gt "1" ]] && OPTIONS="${OPTIONS}~Change Wifi Interface\n~More Options"; } || { OPTIONS="${OPTIONS}~More Options"; }
# Show the wired interface name in the prompt when Ethernet is active.
{ [[ "$WIRED_CON_STATE" == "connected" ]] && PROMPT="${WIRED_INTERFACES_PRODUCT}[$WIRED_INTERFACES]"; } || PROMPT="${WIRELESS_INTERFACES_PRODUCT[WLAN_INT]}[${WIRELESS_INTERFACES[WLAN_INT]}]"
SELECTION=$(echo -e "$OPTIONS" | wofi_cmd "$OPTIONS" $WIDTH_FIX_MAIN "-a 0")
# Collapse multi-space separators to | then extract the first field (SSID without decorators).
SSID=$(echo "$SELECTION" | sed "s/\s\{2,\}/\|/g" | awk -F "|" '{print $1}')
selection_action
}
function wofi_cmd() {
# Auto-size the wofi window to fit the widest entry.
# $1 = option list for width calculation, $2 = width adjustment, $3/$4 = extra flags.
# WIDTH is halved because wofi uses character-width em units on a proportional font.
{ [[ -n "${1}" ]] && WIDTH=$(echo -e "$1" | awk '{print length}' | sort -n | tail -1) && ((WIDTH += $2)) && ((WIDTH = WIDTH / 2)); } || { ((WIDTH = $2 / 2)); }
# -dmenu: read from stdin; -i: case-insensitive; --normal-window: don't use a special surface.
# -theme-str injects CSS overrides: sets window width and the prompt colon label.
wofi -dmenu -i --normal-window=false -location "$LOCATION" -yoffset "$Y_AXIS" -xoffset "$X_AXIS" $3 -theme "$RASI_DIR" -theme-str 'window{width: '$WIDTH'em;}textbox-prompt-colon{str:"'$PROMPT':";}'"$4"''
}
function change_wireless_interface() {
# If exactly two cards exist, toggle between index 0 and 1 without showing a menu.
# Otherwise, present all cards in wofi and let the user pick.
{ [[ ${#WIRELESS_INTERFACES[@]} -eq "2" ]] && { [[ $WLAN_INT -eq "0" ]] && WLAN_INT=1 || WLAN_INT=0; }; } || {
LIST_WLAN_INT=""
for i in "${!WIRELESS_INTERFACES[@]}"; do LIST_WLAN_INT=("${LIST_WLAN_INT[@]}${WIRELESS_INTERFACES_PRODUCT[$i]}[${WIRELESS_INTERFACES[$i]}]\n"); done
# Strip the trailing \n from the last entry to avoid a blank line in wofi.
LIST_WLAN_INT[-1]=${LIST_WLAN_INT[-1]::-2}
CHANGE_WLAN_INT=$(echo -e "${LIST_WLAN_INT[@]}" | wofi_cmd "${LIST_WLAN_INT[@]}" $WIDTH_FIX_STATUS)
# Resolve the selected label back to its index in WIRELESS_INTERFACES.
for i in "${!WIRELESS_INTERFACES[@]}"; do [[ $CHANGE_WLAN_INT == "${WIRELESS_INTERFACES_PRODUCT[$i]}[${WIRELESS_INTERFACES[$i]}]" ]] && WLAN_INT=$i && break; done
}
# Refresh state after the interface switch and redraw the menu.
wireless_interface_state && ethernet_interface_state
wofi_menu
}
function scan() {
# If Wi-Fi is off, turn it on and wait 2 s for the radio to come up before scanning.
[[ "$WIFI_CON_STATE" =~ "unavailable" ]] && change_wifi_state "Wi-Fi" "Enabling Wi-Fi connection" "on" && sleep 2
notification "-t 0 Wifi" "Please Wait Scanning"
# --rescan yes forces an active scan; without it nmcli may return a stale cache.
# awk deduplicates SSIDs; sed cleans up the header and the currently-connected marker.
WIFI_LIST=$(nmcli --fields IN-USE,SSID,SECURITY,BARS device wifi list ifname "${WIRELESS_INTERFACES[WLAN_INT]}" --rescan yes | awk -F' +' '{ if (!seen[$2]++) print}' | sed "s/^IN-USE\s//g" | sed "/*/d" | sed "s/^ *//" | awk '$1!="--" {print}')
wireless_interface_state && ethernet_interface_state
notification "-t 1 Wifi" "Please Wait Scanning"
wofi_menu
}
function change_wifi_state() {
# Toggle the Wi-Fi radio via nmcli: "on" enables it, "off" disables it.
notification "$1" "$2"
nmcli radio wifi "$3"
}
function change_wired_state() {
# Connect or disconnect a wired interface: nmcli device connect/disconnect <iface>.
notification "$1" "$2"
nmcli device "$3" "$4"
}
function net_restart() {
# Hard-cycle the NetworkManager stack; 3 s sleep lets the kernel teardown complete.
notification "$1" "$2"
nmcli networking off && sleep 3 && nmcli networking on
}
function disconnect() {
# Resolve the connection profile name for the active SSID, then bring it down.
# -t (terse) + cut removes the "GENERAL.CONNECTION:" key prefix from nmcli output.
ACTIVE_SSID=$(nmcli -t -f GENERAL.CONNECTION dev show "${WIRELESS_INTERFACES[WLAN_INT]}" | cut -d ':' -f2)
notification "$1" "You're now disconnected from Wi-Fi network '$ACTIVE_SSID'"
nmcli con down id "$ACTIVE_SSID"
}
function check_wifi_connected() {
# Drop any existing connection before attempting a new one; avoids "already connected" errors.
[[ "$(nmcli device status | grep "^${WIRELESS_INTERFACES[WLAN_INT]}." | awk '{print $3}')" == "connected" ]] && disconnect "Connection_Terminated"
}
function connect() {
check_wifi_connected
notification "-t 0 Wi-Fi" "Connecting to $1"
# Try to join the network with the supplied password; report success or failure.
{ [[ $(nmcli dev wifi con "$1" password "$2" ifname "${WIRELESS_INTERFACES[WLAN_INT]}" | grep -c "successfully activated") -eq "1" ]] && notification "Connection_Established" "You're now connected to Wi-Fi network '$1'"; } || notification "Connection_Error" "Connection can not be established"
}
function enter_passwword() {
# Open a password-masked wofi prompt; the placeholder text acts as a "stored key" signal.
PROMPT="Enter_Password" && PASS=$(echo "$PASSWORD_ENTER" | wofi_cmd "$PASSWORD_ENTER" 4 "-password")
}
function enter_ssid() {
# Open a plain text wofi prompt with no pre-filled options for manual SSID entry.
PROMPT="Enter_SSID" && SSID=$(wofi_cmd "" 40)
}
function stored_connection() {
# Re-activate an already-known connection profile without supplying a password.
check_wifi_connected
notification "-t 0 Wi-Fi" "Connecting to $1"
{ [[ $(nmcli dev wifi con "$1" ifname "${WIRELESS_INTERFACES[WLAN_INT]}" | grep -c "successfully activated") -eq "1" ]] && notification "Connection_Established" "You're now connected to Wi-Fi network '$1'"; } || notification "Connection_Error" "Connection can not be established"
}
function ssid_manual() {
# Let the user type an SSID; if a password is entered use it, otherwise try stored credentials.
enter_ssid
[[ -n $SSID ]] && {
enter_passwword
@ -119,10 +171,12 @@ function ssid_manual() {
}
}
function ssid_hidden() {
# Connect to a hidden SSID by creating or reusing an NM connection profile.
enter_ssid
[[ -n $SSID ]] && {
enter_passwword && check_wifi_connected
[[ -n "$PASS" ]] && [[ "$PASS" != "$PASSWORD_ENTER" ]] && {
# Create a new Wi-Fi connection profile, then configure WPA-PSK security on it.
nmcli con add type wifi con-name "$SSID" ssid "$SSID" ifname "${WIRELESS_INTERFACES[WLAN_INT]}"
nmcli con modify "$SSID" wifi-sec.key-mgmt wpa-psk
nmcli con modify "$SSID" wifi-sec.psk "$PASS"
@ -132,59 +186,80 @@ function ssid_hidden() {
}
}
function interface_status() {
# local -n creates a nameref — $1 and $2 are the names of the caller's arrays,
# not their values; this avoids passing large arrays by copy.
local -n INTERFACES=$1 && local -n INTERFACES_PRODUCT=$2
for i in "${!INTERFACES[@]}"; do
CON_STATE=$(nmcli device status | grep "^${INTERFACES[$i]}." | awk '{print $3}')
INT_NAME=${INTERFACES_PRODUCT[$i]}[${INTERFACES[$i]}]
# When connected, show the profile name and IPv4 address; otherwise capitalise the state word.
# awk -F '[:]' splits on colon to strip the "GENERAL.CONNECTION:" prefix.
# awk -F '[:/]' splits on colon or slash to extract the bare IP from "IP4.ADDRESS[1]: x.x.x.x/24".
[[ "$CON_STATE" == "connected" ]] && STATUS="$INT_NAME:\n\t$(nmcli -t -f GENERAL.CONNECTION dev show "${INTERFACES[$i]}" | awk -F '[:]' '{print $2}') ~ $(nmcli -t -f IP4.ADDRESS dev show "${INTERFACES[$i]}" | awk -F '[:/]' '{print $2}')" || STATUS="$INT_NAME: ${CON_STATE^}"
echo -e "${STATUS}"
done
}
function status() {
# Build a combined status view of all wired and wireless interfaces plus any active VPN.
OPTIONS=""
[[ ${#WIRED_INTERFACES[@]} -ne "0" ]] && ETH_STATUS="$(interface_status WIRED_INTERFACES WIRED_INTERFACES_PRODUCT)" && OPTIONS="${OPTIONS}${ETH_STATUS}"
[[ ${#WIRELESS_INTERFACES[@]} -ne "0" ]] && WLAN_STATUS="$(interface_status WIRELESS_INTERFACES WIRELESS_INTERFACES_PRODUCT)" && { [[ -n ${OPTIONS} ]] && OPTIONS="${OPTIONS}\n${WLAN_STATUS}" || OPTIONS="${OPTIONS}${WLAN_STATUS}"; }
# -g NAME,TYPE filters to name:type pairs; awk/sed extract just the name for vpn-type connections.
ACTIVE_VPN=$(nmcli -g NAME,TYPE con show --active | awk '/:vpn/' | sed 's/:vpn.*//g')
[[ -n $ACTIVE_VPN ]] && OPTIONS="${OPTIONS}\n${ACTIVE_VPN}[VPN]: $(nmcli -g ip4.address con show "${ACTIVE_VPN}" | awk -F '[:/]' '{print $1}')"
# "mainbox{children:[listview];}" hides the search field for a read-only status display.
echo -e "$OPTIONS" | wofi_cmd "$OPTIONS" $WIDTH_FIX_STATUS "" "mainbox{children:[listview];}"
}
function share_pass() {
# Retrieve the current network's SSID and password via nmcli, then display them.
# -oP with a lookbehind extracts only the value after "SSID: " / "Password: ".
SSID=$(nmcli dev wifi show-password | grep -oP '(?<=SSID: ).*' | head -1)
PASSWORD=$(nmcli dev wifi show-password | grep -oP '(?<=Password: ).*' | head -1)
OPTIONS="SSID: ${SSID}\nPassword: ${PASSWORD}"
# Offer QR-code generation only when qrencode is installed.
[[ -x "$(command -v qrencode)" ]] && OPTIONS="${OPTIONS}\n~QrCode"
SELECTION=$(echo -e "$OPTIONS" | wofi_cmd "$OPTIONS" $WIDTH_FIX_STATUS "-a -1" "mainbox{children:[listview];}")
selection_action
}
function gen_qrcode() {
# Generate a WPA QR-code PNG using qrencode and display it inside a wofi window.
# -l H: high error correction; -s 25: module size 25px; -m 2: 2-module border; --dpi=192: HiDPI.
DIRECTIONS=("Center" "Northwest" "North" "Northeast" "East" "Southeast" "South" "Southwest" "West")
[[ -e $QRCODE_DIR$SSID.png ]] || qrencode -t png -o $QRCODE_DIR$SSID.png -l H -s 25 -m 2 --dpi=192 "WIFI:S:""$SSID"";T:""$(nmcli dev wifi show-password | grep -oP '(?<=Security: ).*' | head -1)"";P:""$PASSWORD"";;"
# Open a decorationless wofi window at the chosen corner and fill it with the QR image.
wofi_cmd "" "0" "" "entry{enabled:false;}window{location:"${DIRECTIONS[QRCODE_LOCATION]}";border-radius:6mm;padding:1mm;width:100mm;height:100mm;
background-image:url(\"$QRCODE_DIR$SSID.png\",both);}"
}
function manual_hidden() {
# Sub-menu to choose between a visible-but-unlisted (manual) or a truly hidden SSID.
OPTIONS="~Manual\n~Hidden" && SELECTION=$(echo -e "$OPTIONS" | wofi_cmd "$OPTIONS" $WIDTH_FIX_STATUS "" "mainbox{children:[listview];}")
selection_action
}
function vpn() {
# If a VPN is already active offer to deactivate it; otherwise list all known VPN profiles.
ACTIVE_VPN=$(nmcli -g NAME,TYPE con show --active | awk '/:vpn/' | sed 's/:vpn.*//g')
[[ $ACTIVE_VPN ]] && OPTIONS="~Deactive $ACTIVE_VPN" || OPTIONS="$(nmcli -g NAME,TYPE connection | awk '/:vpn/' | sed 's/:vpn.*//g')"
VPN_ACTION=$(echo -e "$OPTIONS" | wofi_cmd "$OPTIONS" "$WIDTH_FIX_STATUS" "" "mainbox {children:[listview];}")
[[ -n "$VPN_ACTION" ]] && { { [[ "$VPN_ACTION" =~ "~Deactive" ]] && nmcli connection down "$ACTIVE_VPN" && notification "VPN_Deactivated" "$ACTIVE_VPN"; } || {
# 2>/dev/null suppresses "Error: ... already active" messages from nmcli.
notification "-t 0 Activating_VPN" "$VPN_ACTION" && VPN_OUTPUT=$(nmcli connection up "$VPN_ACTION" 2>/dev/null)
{ [[ $(echo "$VPN_OUTPUT" | grep -c "Connection successfully activated") -eq "1" ]] && notification "VPN_Successfully_Activated" "$VPN_ACTION"; } || notification "Error_Activating_VPN" "Check your configuration for $VPN_ACTION"
}; }
}
function more_options() {
# Secondary menu: share password (only when connected), status, restart, VPN, editor.
OPTIONS=""
[[ "$WIFI_CON_STATE" == "connected" ]] && OPTIONS="~Share Wifi Password\n"
OPTIONS="${OPTIONS}~Status\n~Restart Network"
# Only add the VPN entry when at least one VPN profile is configured.
[[ $(nmcli -g NAME,TYPE connection | awk '/:vpn/' | sed 's/:vpn.*//g') ]] && OPTIONS="${OPTIONS}\n~VPN"
# nm-connection-editor is a GTK app from NetworkManager-applet — only show if installed.
[[ -x "$(command -v nm-connection-editor)" ]] && OPTIONS="${OPTIONS}\n~Open Connection Editor"
SELECTION=$(echo -e "$OPTIONS" | wofi_cmd "$OPTIONS" "$WIDTH_FIX_STATUS" "" "mainbox {children:[listview];}")
selection_action
}
function selection_action() {
# Central dispatcher: map each menu label to its handler function.
case "$SELECTION" in
"~Disconnect") disconnect "Connection_Terminated" ;;
"~Scan") scan ;;
@ -197,6 +272,7 @@ function selection_action() {
"~Wi-Fi Off") change_wifi_state "Wi-Fi" "Disabling Wi-Fi connection" "off" ;;
"~Eth Off") change_wired_state "Ethernet" "Disabling Wired connection" "disconnect" "${WIRED_INTERFACES}" ;;
"~Eth On") change_wired_state "Ethernet" "Enabling Wired connection" "connect" "${WIRED_INTERFACES}" ;;
# Informational entries — user clicked a status label, nothing to do.
"***Wi-Fi Disabled***") ;;
"***Wired Unavailable***") ;;
"***Wired Initializing***") ;;
@ -207,8 +283,12 @@ function selection_action() {
"~Open Connection Editor") nm-connection-editor ;;
"~VPN") vpn ;;
*)
# Default: treat the selection as a network from WIFI_LIST.
[[ -n "$SELECTION" ]] && [[ "$WIFI_LIST" =~ .*"$SELECTION".* ]] && {
# When SSID is "*" the user selected the currently-active network row (which starts with *).
[[ "$SSID" == "*" ]] && SSID=$(echo "$SELECTION" | sed "s/\s\{2,\}/\|/g " | awk -F "|" '{print $3}')
# If already connected to this SSID just bring the profile up (re-apply settings).
# Otherwise prompt for a password if the network is WPA2/WEP secured, then connect.
{ [[ "$ACTIVE_SSID" == "$SSID" ]] && nmcli con up "$SSID" ifname "${WIRELESS_INTERFACES[WLAN_INT]}"; } || {
[[ "$SELECTION" =~ "WPA2" ]] || [[ "$SELECTION" =~ "WEP" ]] && enter_passwword
{ [[ -n "$PASS" ]] && [[ "$PASS" != "$PASSWORD_ENTER" ]] && connect "$SSID" "$PASS"; } || stored_connection "$SSID"
@ -218,6 +298,7 @@ function selection_action() {
esac
}
function main() {
# Entry point: load config and launch the menu.
initialization && wofi_menu
}
main

View File

@ -1,15 +1,39 @@
// ─────────────────────────────────────────────────────────────────────────────
// ~/.config/niri/config.kdl
// Niri scrollable-tiling Wayland compositor — CyberQueer config
// https://github.com/YaLTeR/niri/wiki/Configuration:-Overview
//
// PURPOSE: Root configuration entry-point for the niri compositor.
// This file does not contain settings directly — it delegates all
// configuration sections to separate module files so each concern
// (input, outputs, layout, etc.) can be edited independently.
//
// STRUCTURE: niri supports a flat `include` directive that merges KDL files
// before parsing, so the split is purely organisational.
//
// REFERENCE: https://github.com/YaLTeR/niri/wiki/Configuration:-Overview
// ─────────────────────────────────────────────────────────────────────────────
include "modules/input.kdl"
include "modules/outputs.kdl"
include "modules/layout.kdl"
include "modules/animations.kdl"
include "modules/environment.kdl"
include "modules/autostart.kdl"
include "modules/window-rules.kdl"
include "modules/binds.kdl"
// ── Module includes ───────────────────────────────────────────────────────────
// Each file below is merged verbatim into this config at parse time.
// The order matters only when settings in later files override earlier ones.
include "modules/input.kdl" // Keyboard, touchpad, mouse, focus behaviour
include "modules/outputs.kdl" // Monitor/display declarations and positions
include "modules/layout.kdl" // Gaps, column widths, focus ring, struts
include "modules/animations.kdl" // Spring / easing curves for all transitions
include "modules/environment.kdl" // Env vars passed to every child process + cursor
include "modules/autostart.kdl" // Programs launched when niri starts
include "modules/window-rules.kdl" // Per-app floating, sizing, opacity overrides
include "modules/binds.kdl" // All keyboard and mouse bindings
// ── Global preferences ────────────────────────────────────────────────────────
// prefer-no-csd: Tell clients to draw without client-side decorations (title bars).
// WHY: niri renders its own focus ring / border; double-decorations look wrong.
// Apps that respect this (most GTK/Qt apps) gain a cleaner appearance.
prefer-no-csd
// screenshot-path: Where niri saves full-compositor screenshots (Print key).
// WHY: Organise screenshots in a dedicated folder with a human-readable timestamp.
// strftime-style % tokens are expanded at capture time.
screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"

View File

@ -1,23 +1,70 @@
// ── Animations ────────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────────────────────
// modules/animations.kdl — Niri animation settings (CyberQueer profile)
//
// PURPOSE: Defines easing/spring curves for every animated transition in niri:
// window open/close/move, workspace switching, and UI overlays.
//
// SPRING PHYSICS MODEL: niri uses a physically-based damped spring rather than
// a fixed-duration curve. Three parameters control it:
//
// damping-ratio — how quickly oscillation decays after the spring overshoots.
// 1.0 = critically damped (no bounce, fastest settle).
// <1.0 = underdamped (bouncy). >1.0 = overdamped (sluggish).
// stiffness — spring constant; how hard the spring pulls toward the target.
// Higher = snappier motion; lower = floaty/elastic.
// epsilon — the displacement threshold below which animation stops.
// Smaller = more precise end position (negligible CPU cost).
// ─────────────────────────────────────────────────────────────────────────────
animations {
// window-open: played when a new window first appears on screen.
// damping-ratio=0.9: very slightly underdamped — tiny implicit "settle"
// without a visible bounce, making the open feel snappy and alive.
// stiffness=800: high stiffness keeps the open fast (< ~120ms perceived).
// epsilon=0.0001: stops at 0.01% residual — visually at rest immediately.
window-open {
spring damping-ratio=0.9 stiffness=800 epsilon=0.0001
}
// window-close: played when a window is destroyed/hidden.
// Mirrors window-open so open and close feel symmetric.
window-close {
spring damping-ratio=0.9 stiffness=800 epsilon=0.0001
}
// window-movement: played when a window or column is repositioned within
// the scrollable layout (e.g. Mod+Shift+H/L, consume/expel into column).
window-movement {
spring damping-ratio=0.9 stiffness=800 epsilon=0.0001
}
// horizontal-view-movement: played when the viewport scrolls horizontally
// to follow the focused column. This is the signature "infinite rail"
// feel that distinguishes niri from traditional tiling compositors.
horizontal-view-movement {
spring damping-ratio=0.9 stiffness=800 epsilon=0.0001
}
// workspace-switch: played when switching between vertical workspaces
// (Mod+Ctrl+J/K, Mod+10). Consistent spring keeps it feeling cohesive.
workspace-switch {
spring damping-ratio=0.9 stiffness=800 epsilon=0.0001
}
// config-notification-open-close: the small "Configuration reloaded" toast
// shown after a hot-reload. Same spring for a unified UI feel.
config-notification-open-close {
spring damping-ratio=0.9 stiffness=800 epsilon=0.0001
}
// screenshot-ui-open: the interactive region-selection overlay.
// WHY different curve: this is a modal overlay, not a layout element.
// A fixed-duration ease-out-cubic gives a crisp "snap into place" feel
// that communicates "you are now in screenshot mode" more clearly than a
// spring would.
// duration-ms 200: fast enough not to interrupt workflow.
// curve "ease-out-cubic": starts fast, decelerates smoothly to rest.
screenshot-ui-open {
duration-ms 200
curve "ease-out-cubic"

View File

@ -1,13 +1,83 @@
// ── Autostart ─────────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────────────────────
// modules/autostart.kdl — Programs launched when niri starts
//
// PURPOSE: Lists every daemon, applet, and background service required for a
// complete CyberQueer desktop session. niri's spawn-at-startup
// launches each command once; there is no restart logic, so crashed
// daemons stay dead until the compositor is restarted.
//
// NOTE: Arguments are passed via execvp (not a shell). Anything needing shell
// features (tilde expansion, pipes, globs) must use `bash -c "..."`.
// ─────────────────────────────────────────────────────────────────────────────
// monitorhandler.sh — sets the wallpaper (swaybg) and launches eww bar.
// WHY first: wallpaper and status bar should appear as early as possible so
// the desktop looks ready before the user interacts with it.
spawn-at-startup "bash" "-c" "~/.config/scripts/monitorhandler.sh"
// vicinae — application launcher daemon that keeps a hot process/app cache.
// "vicinae server" starts the background index so that Mod+R (toggle) is
// near-instantaneous rather than waiting for a cold startup scan.
spawn-at-startup "bash" "-c" "vicinae server"
// bluetooth-applet.sh — thin wrapper around blueman-applet.
// WHY a wrapper script: allows environment setup or guard conditions before
// the applet is spawned (e.g. waiting for bluetoothd to be ready).
spawn-at-startup "bash" "-c" "~/.config/scripts/bluetooth-applet.sh"
// ulwatchdog.sh — keeps ulauncher alive (polls every 5 s and restarts it).
// WHY: ulauncher serves as a secondary launcher; the watchdog ensures it is
// always available so the first Mod+R after a crash doesn't silently fail.
spawn-at-startup "bash" "-c" "~/.config/scripts/ulwatchdog.sh"
// udiskie — automounter for removable media (USB drives, SD cards, etc.).
// -t → show a system-tray icon for manual mount/unmount actions
// -m nested → display a nested submenu in the tray (cleaner than flat list)
// -n → suppress desktop notifications (dunst handles those instead)
spawn-at-startup "udiskie" "-t" "-m" "nested" "-n"
// xfce-polkit — PolicyKit authentication agent (privilege escalation dialog).
// WHY: without a running polkit agent, GUI applications that request root
// privileges (e.g. gparted, package managers) will silently fail or crash.
// xfce-polkit is lightweight and works on any Wayland session.
spawn-at-startup "xfce-polkit"
// gammastep — Wayland colour-temperature daemon (like redshift).
// -O 4500: apply a fixed 4500 K warm-white colour temperature at startup.
// WHY fixed value: avoids needing a GPS/location service. 4500 K is a
// comfortable default for mixed day/evening use. binds.kdl provides
// shortcuts (Mod+Ctrl+X/W/A/Q/S) to change temperature on the fly.
spawn-at-startup "bash" "-c" "gammastep -O 4500"
// nm-applet — NetworkManager system-tray icon.
// WHY: provides quick access to Wi-Fi, VPN, and wired network management
// without keeping a full settings panel open.
spawn-at-startup "nm-applet"
// dunst — notification daemon (implements the freedesktop.org spec).
// WHY: lightweight alternative to mako/swaync; integrates tightly with
// dunstctl for the scripted close-all binding in binds.kdl (Mod+Ctrl+C).
spawn-at-startup "dunst"
// swayidle — idle-based screen locker and suspend manager.
// Flags and timeouts:
// -w → wait for each command to finish before
// acknowledging the idle state (prevents races)
// timeout 300 'swaylock -f' → lock screen after 5 minutes idle
// timeout 600 'systemctl suspend' → suspend system after 10 minutes idle
// before-sleep 'swaylock -f' → lock before every suspend/hibernate,
// including lid-close events
// WHY swaylock -f: the -f (fork) flag lets swaylock daemonise itself so
// swayidle doesn't block waiting for it to exit.
spawn-at-startup "bash" "-c" "swayidle -w timeout 300 'swaylock -f' timeout 600 'systemctl suspend' before-sleep 'swaylock -f'"
// presence-detect.sh — webcam-based automatic idle inhibitor.
// WHY: detects when the user is physically at the desk (via camera) and holds
// the idle inhibitor active so the screen doesn't lock unnecessarily.
// Complements the manual caffeine.sh toggle (Mod+Shift+C).
spawn-at-startup "bash" "-c" "~/.config/scripts/presence-detect.sh"
// blueman-applet — Bluetooth tray icon with device management UI.
// WHY separate from nm-applet: blueman exposes pairing, profile switching,
// and per-device settings that NetworkManager's Bluetooth support omits.
spawn-at-startup "blueman-applet"

View File

@ -1,74 +1,158 @@
// ── Keybindings ───────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────────────────────
// modules/binds.kdl — Keyboard and mouse bindings for niri
//
// Modifier semantics (directional keys):
// Mod + dir → focus (window or column)
// Mod + Shift + dir → move selected window/column
// Mod + Ctrl + dir → navigate workspaces
// Mod + Ctrl+Shift + dir → move window to workspace
// Mod + Alt + dir → resize (column width / window height)
// PURPOSE: Central definition of every user-triggered action in the niri
// session. Bindings are grouped by function so related keys are easy
// to find and modify.
//
// MODIFIER KEY ("Mod"): Defaults to the Super / Windows key.
//
// Modifier semantics for directional keys:
// Mod + dir → focus (move focus to window or column)
// Mod + Shift + dir → move selected window or column in that direction
// Mod + Ctrl + dir → navigate workspaces (up/down)
// Mod + Ctrl+Shift + dir → move the focused column to a workspace
// Mod + Alt + dir → resize (column width with H/L, window height with J/K)
//
// allow-when-locked=true: the binding fires even when swaylock is active.
// WHY: media controls and brightness must work on the lock screen.
// ─────────────────────────────────────────────────────────────────────────────
binds {
// ── Applications ──────────────────────────────────────────────────────────
// Mod+T: open a standard kitty terminal.
Mod+T { spawn "kitty"; }
// Mod+Shift+T: open cool-retro-term with the CRT profile from this repo.
// WHY: retro CRT aesthetic for a cyberqueer vibe; kept separate from the
// main terminal so normal workflows aren't forced through the slow CRT font.
Mod+Shift+T { spawn "bash" "-c" "cool-retro-term -p ~/Dotfiles/desktopenvs/niri/CRT"; }
// Mod+M: open Neovim inside kitty (quick editor shortcut).
Mod+M { spawn "bash" "-c" "kitty -e nvim"; }
// Mod+E: open Thunar file manager (primary GUI file manager).
Mod+E { spawn "thunar"; }
// Mod+Alt+E: open PCManFM-Qt as an alternative file manager.
// WHY alt key: secondary tool; not used daily but useful as a fallback.
Mod+Alt+E { spawn "pcmanfm-qt"; }
// Mod+X: open wofi in "run" mode (raw binary launcher, no .desktop files).
Mod+X { spawn "bash" "-c" "wofi --show=run"; }
// Mod+N: open Nextcloud desktop client for cloud sync management.
Mod+N { spawn "nextcloud"; }
// Mod+I: open iwmenu (Wi-Fi network picker) using the walker launcher UI.
Mod+I { spawn "bash" "-c" "iwmenu --launcher walker"; }
// Mod+Alt+I: open bzmenu (Bluetooth device picker) with walker UI.
Mod+Alt+I { spawn "bash" "-c" "bzmenu --launcher walker"; }
// Mod+Shift+I: open nm-connection-editor for advanced network configuration.
Mod+Shift+I { spawn "nm-connection-editor"; }
// Mod+R / Mod+Return / Ctrl+Shift+R: toggle the vicinae launcher overlay.
// WHY three bindings: muscle-memory coverage from different contexts
// (Mod+R mirrors rofi habit; Return is ergonomic; Ctrl+Shift+R works in
// some terminal apps where Mod is captured).
Mod+R { spawn "bash" "-c" "vicinae toggle"; }
Mod+Return { spawn "bash" "-c" "vicinae toggle"; }
Mod+Shift+R { spawn "bash" "-c" "wofi --show drun"; }
Ctrl+Shift+R { spawn "bash" "-c" "vicinae toggle"; }
Mod+F { spawn "bash" "-c" "~/.config/scripts/wofi-file-search.sh"; }
Mod+Shift+F { spawn "bash" "-c" "~/.config/scripts/foldersearch.sh"; }
Mod+Alt+F { spawn "wofi-calc"; }
// Mod+Shift+R: open wofi in drun mode (.desktop application launcher).
Mod+Shift+R { spawn "bash" "-c" "wofi --show drun"; }
// File search shortcuts
Mod+F { spawn "bash" "-c" "~/.config/scripts/wofi-file-search.sh"; } // fuzzy file search
Mod+Shift+F { spawn "bash" "-c" "~/.config/scripts/foldersearch.sh"; } // folder search
Mod+Alt+F { spawn "wofi-calc"; } // calculator in wofi
// Mod+S: open PulseAudio volume control (pavucontrol) for audio routing.
Mod+S { spawn "pavucontrol"; }
// Mod+U: open btop system monitor inside kitty.
Mod+U { spawn "bash" "-c" "kitty -e btop"; }
// Mod+W: open the TUI wallpaper picker (swaybg-based, uses kitty icat).
Mod+W { spawn "bash" "-c" "kitty -e ~/.config/scripts/wallpaper-picker ~/Pictures"; }
// Mod+Ctrl+R: open the amssh SSH session picker inside kitty.
Mod+Ctrl+R { spawn "bash" "-c" "kitty -e ~/.config/scripts/amssh"; }
// Mod+F1: open the help/keybindings cheatsheet in a kitty window.
Mod+F1 { spawn "bash" "-c" "kitty -e ~/.config/scripts/helpmenu.sh"; }
// Mod+Ctrl+T: open a countdown/interval timer picker inside kitty.
Mod+Ctrl+T { spawn "bash" "-c" "kitty -e ~/.config/scripts/timer-pick"; }
// Mod+Shift+F1: open the niri config file directly in nvim for quick edits.
Mod+Shift+F1 { spawn "bash" "-c" "kitty -e nvim ~/.config/niri/config.kdl"; }
// Mod+Ctrl+P: start/stop screen recording (screenrec.sh toggles ffmpeg).
Mod+Ctrl+P { spawn "bash" "-c" "~/.config/scripts/screenrec.sh"; }
// App drawer
// App drawer: nwg-drawer (grid of apps + power menu) — two bindings for
// ergonomic coverage.
Mod+D { spawn "bash" "-c" "~/.config/scripts/drawer.sh"; }
Mod+Shift+A { spawn "bash" "-c" "~/.config/scripts/drawer.sh"; }
// ── Screenshots ───────────────────────────────────────────────────────────
// Print / Mod+P: interactive region/window screenshot (saved to Pictures/Screenshots).
Print { screenshot; }
Mod+P { screenshot; }
// Mod+Shift+P: full-screen (current output) screenshot without selection UI.
Mod+Shift+P { screenshot-screen; }
// ── Window Management ─────────────────────────────────────────────────────
// Mod+Q / Mod+Shift+Q: close the focused window (sends WM_DELETE_WINDOW).
// WHY two bindings: Shift variant is a safe fallback if Q is captured by an app.
Mod+Q { close-window; }
Mod+Shift+Q { close-window; }
// Mod+V: toggle the focused window between tiled and floating mode.
Mod+V { toggle-window-floating; }
// Mod+Shift+V / Mod+C: centre the focused column on screen.
// WHY two bindings: muscle memory from different compositors (hyprland uses C).
Mod+Shift+V { center-column; }
Mod+C { center-column; }
// Mod+Ctrl+M: cycle through available layout algorithms (niri currently
// offers one, but this future-proofs the setup).
Mod+Ctrl+M { switch-layout "next"; }
// ── Niri layout actions ───────────────────────────────────────────────────
// Mod+Backspace: cycle through preset column widths (1/3, 1/2, 2/3, full).
// Widths defined in layout.kdl → preset-column-widths.
Mod+Backspace { switch-preset-column-width; }
// Mod+Shift+Backspace: toggle the focused column between its current width
// and the full available width (maximise without going fullscreen).
Mod+Shift+Backspace { maximize-column; }
// Mod+Ctrl+Backspace: true fullscreen (window covers the whole output,
// hiding the eww bar).
Mod+Ctrl+Backspace { fullscreen-window; }
// Consume/expel from column (hyprlua group analog)
// Column stacking (niri's "group" equivalent to hyprland's window groups):
// Mod+A: pull the window to the right of the focused column into it (stack below).
// Mod+Y: eject the bottom window of a multi-window column to its own column.
// Mod+Alt+C/V: consume/expel the adjacent window left or right.
Mod+A { consume-window-into-column; }
Mod+Y { expel-window-from-column; }
Mod+Alt+C { consume-or-expel-window-left; }
Mod+Alt+V { consume-or-expel-window-right; }
// ── Focus ─────────────────────────────────────────────────────────────────
// ── Focus — Vim-style (H/J/K/L) + arrow keys ─────────────────────────────
// H/L navigate between columns; J/K navigate between stacked windows within
// a column. Arrow keys are duplicated for non-vim users.
Mod+H { focus-column-left; }
Mod+L { focus-column-right; }
Mod+J { focus-window-down; }
@ -78,11 +162,17 @@ binds {
Mod+Down { focus-window-down; }
Mod+Up { focus-window-up; }
// Mod+Tab: focus the next window down, or the first window of the next column.
// WHY: provides a natural Alt+Tab-like cycling without needing a separate switcher.
Mod+Tab { focus-window-down-or-column-right; }
Mod+Shift+Tab { focus-window-up-or-column-left; }
// Mod+Shift+Return: toggle the workspace overview (bird's-eye view of all
// workspaces and their windows).
Mod+Shift+Return { toggle-overview; }
// ── Move Window ───────────────────────────────────────────────────────────
// ── Move Window — Vim-style + arrow keys ──────────────────────────────────
// Moves the focused column/window in the specified direction within the layout.
Mod+Shift+H { move-column-left; }
Mod+Shift+L { move-column-right; }
Mod+Shift+J { move-window-down; }
@ -92,12 +182,17 @@ binds {
Mod+Shift+Down { move-window-down; }
Mod+Shift+Up { move-window-up; }
// Mouse drag / resize
// Mouse drag and resize:
// Button272 = left mouse button; Button273 = right mouse button.
// Mod+LMB: drag window to move it (floating or reorder in tiling).
// Mod+RMB / Mod+Shift+LMB: drag to resize window.
Mod+Button272 { move-window-with-pointer; }
Mod+Button273 { resize-window-with-pointer; }
Mod+Shift+Button272 { resize-window-with-pointer; }
// ── Resize ────────────────────────────────────────────────────────────────
// ── Resize — Mod+Alt + Vim-style + arrow keys ─────────────────────────────
// Adjusts column width (H/L) or window height within a column (J/K).
// 10% increments are coarse enough to be useful without needing many presses.
Mod+Alt+L { set-column-width "+10%"; }
Mod+Alt+H { set-column-width "-10%"; }
Mod+Alt+K { set-window-height "-10%"; }
@ -107,7 +202,8 @@ binds {
Mod+Alt+Up { set-window-height "-10%"; }
Mod+Alt+Down { set-window-height "+10%"; }
// ── Workspaces — direct ───────────────────────────────────────────────────
// ── Workspaces — direct jump (Mod+number) ────────────────────────────────
// Jumps directly to workspace N (1-indexed; Mod+0 = workspace 10).
Mod+1 { focus-workspace 1; }
Mod+2 { focus-workspace 2; }
Mod+3 { focus-workspace 3; }
@ -119,6 +215,7 @@ binds {
Mod+9 { focus-workspace 9; }
Mod+0 { focus-workspace 10; }
// Mod+Shift+number: move the focused column to workspace N.
Mod+Shift+1 { move-column-to-workspace 1; }
Mod+Shift+2 { move-column-to-workspace 2; }
Mod+Shift+3 { move-column-to-workspace 3; }
@ -131,7 +228,9 @@ binds {
Mod+Shift+0 { move-column-to-workspace 10; }
// ── Workspaces — relative navigation (Ctrl + direction) ──────────────────
// J/K and Down/Up are the canonical vertical workspace nav
// niri workspaces stack vertically, so J/K and Down/Up are the canonical
// directions. H/L and Left/Right are aliased for users habituated to
// horizontal workspace navigation from other compositors.
Mod+Ctrl+J { focus-workspace-down; }
Mod+Ctrl+K { focus-workspace-up; }
Mod+Ctrl+Down { focus-workspace-down; }
@ -142,7 +241,9 @@ binds {
Mod+Ctrl+Right { focus-workspace-down; }
Mod+Ctrl+Left { focus-workspace-up; }
// ── Workspaces — move window (Ctrl+Shift + direction) ────────────────────
// ── Workspaces — move column (Ctrl+Shift + direction) ────────────────────
// Sends the focused column to the next/previous workspace while staying on
// the current workspace (the user does not follow the window).
Mod+Ctrl+Shift+J { move-column-to-workspace-down; }
Mod+Ctrl+Shift+K { move-column-to-workspace-up; }
Mod+Ctrl+Shift+Down { move-column-to-workspace-down; }
@ -152,68 +253,129 @@ binds {
Mod+Ctrl+Shift+Right { move-column-to-workspace-down; }
Mod+Ctrl+Shift+Left { move-column-to-workspace-up; }
// Scroll workspaces
// Mouse wheel workspace scrolling (with Mod held):
// cooldown-ms=150: debounce rapid wheel events to avoid skipping workspaces.
Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; }
Mod+WheelScrollUp cooldown-ms=150 { focus-workspace-up; }
// Shift+wheel: moves the column to the target workspace instead of focusing.
Mod+Shift+WheelScrollDown { move-column-to-workspace-down; }
Mod+Shift+WheelScrollUp { move-column-to-workspace-up; }
// Horizontal scroll → column navigation
// Horizontal scroll (trackpad two-finger swipe or horizontal wheel):
// scrolls the focused column list left/right.
Mod+WheelScrollRight { focus-column-right; }
Mod+WheelScrollLeft { focus-column-left; }
// Volume keys as workspace nav
// Volume rocker keys repurposed as workspace nav when held with Mod.
// WHY: on compact keyboards / tablets without Ctrl, the volume buttons
// offer an ergonomic one-handed workspace switch.
Mod+XF86AudioRaiseVolume { focus-workspace-down; }
Mod+XF86AudioLowerVolume { focus-workspace-up; }
Mod+Shift+XF86AudioRaiseVolume { move-column-to-workspace-down; }
Mod+Shift+XF86AudioLowerVolume { move-column-to-workspace-up; }
// ── Overview / scratchpad analog ──────────────────────────────────────────
// ── Overview / recent-window scratchpad analog ────────────────────────────
// focus-workspace-with-recent-window: jump to whichever workspace holds the
// most recently focused window (like a "last workspace" toggle).
Mod+Space { focus-workspace-with-recent-window; }
// Mod+Shift+Space: move the current column to that "recent" workspace.
Mod+Shift+Space { move-column-to-workspace-with-recent-window; }
// Overview toggle (bird's-eye all-workspaces view):
Mod+Ctrl+Space { toggle-overview; }
// Mute button + Mod as an additional overview toggle (useful on tablets).
Mod+XF86AudioMute { toggle-overview; }
Mod+Shift+XF86AudioMute { move-column-to-workspace-with-recent-window; }
// ── Bar / UI ──────────────────────────────────────────────────────────────
// Mod+Z: toggle (show/hide) the eww status bar on the current output.
Mod+Z { spawn "bash" "-c" "~/.config/scripts/togglebar.sh"; }
// Mod+Ctrl+B: force-reload eww (useful after a config change).
Mod+Ctrl+B { spawn "bash" "-c" "eww reload"; }
// ── Audio ─────────────────────────────────────────────────────────────────
// ── Audio (media keys) ────────────────────────────────────────────────────
// allow-when-locked=true: these fire even when swaylock is showing.
// WHY: volume and media control should always be accessible.
//
// wpctl: WirePlumber controller (PipeWire's high-level volume API).
// -l 1.4 caps volume at 140% to protect speakers; 5% steps are fine-grained.
// @DEFAULT_AUDIO_SINK@: targets whatever the current default output is.
XF86AudioRaiseVolume allow-when-locked=true { spawn "wpctl" "set-volume" "-l" "1.4" "@DEFAULT_AUDIO_SINK@" "5%+"; }
XF86AudioLowerVolume allow-when-locked=true { spawn "wpctl" "set-volume" "-l" "1.4" "@DEFAULT_AUDIO_SINK@" "5%-"; }
XF86AudioMute allow-when-locked=true { spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SINK@" "toggle"; }
XF86AudioMicMute allow-when-locked=true { spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SOURCE@" "toggle"; }
// playerctl targets Spotify and VLC; other players won't be accidentally paused.
XF86AudioPlay allow-when-locked=true { spawn "playerctl" "play-pause" "-p" "spotify,vlc"; }
// ── Brightness ────────────────────────────────────────────────────────────
// bri: a minimal backlight helper that adjusts the kernel backlight sysfs node.
// allow-when-locked=true: brightness must work on the lock screen.
XF86MonBrightnessUp allow-when-locked=true { spawn "bri" "--up"; }
XF86MonBrightnessDown allow-when-locked=true { spawn "bri" "--down"; }
// ── Color temperature (gammastep presets) ─────────────────────────────────
// ── Colour temperature presets (gammastep) ────────────────────────────────
// Each binding kills the running gammastep instance and starts a new one at
// a different Kelvin value. WHY killall first: gammastep doesn't support
// live reconfiguration via IPC, so we replace the process entirely.
// 6500 K = neutral daylight white (no filter)
// 5000 K = slight warmth
// 4000 K = warm white (default startup value)
// 3000 K = amber
// 2700 K = candlelight / maximum eye strain reduction
Mod+Ctrl+X { spawn "bash" "-c" "killall gammastep; gammastep -O 6500"; }
Mod+Ctrl+W { spawn "bash" "-c" "killall gammastep; gammastep -O 5000"; }
Mod+Ctrl+A { spawn "bash" "-c" "killall gammastep; gammastep -O 4000"; }
Mod+Ctrl+Q { spawn "bash" "-c" "killall gammastep; gammastep -O 3000"; }
Mod+Ctrl+S { spawn "bash" "-c" "killall gammastep; gammastep -O 2700"; }
// ── Misc ──────────────────────────────────────────────────────────────────
// ── Miscellaneous ─────────────────────────────────────────────────────────
// chamel — a privacy/stealth overlay tool:
// toggle → show/hide the overlay
// clear → remove content but keep overlay active
// clear-and-deactivate → remove content and deactivate overlay entirely
Mod+Ctrl+I { spawn "chamel" "toggle"; }
Mod+Ctrl+U { spawn "chamel" "clear"; }
Mod+Ctrl+Z { spawn "chamel" "clear-and-deactivate"; }
// Mod+Ctrl+C: dismiss all pending dunst notifications.
Mod+Ctrl+C { spawn "dunstctl" "close-all"; }
// Mod+Ctrl+G: toggle on-screen keyboard (for touch/tablet use).
Mod+Ctrl+G { spawn "bash" "-c" "~/.config/scripts/onscreenkb.sh"; }
// Mod+Shift+C: toggle caffeine (idle inhibitor) — prevents screen lock/sleep.
Mod+Shift+C { spawn "bash" "-c" "~/.config/scripts/caffeine.sh"; }
// Mod+Shift+B: open biometrics enrollment wizard in a kitty window.
Mod+Shift+B { spawn "bash" "-c" "kitty -e ~/.config/scripts/enroll-biometrics.sh"; }
// Mod+Shift+X: toggle touchpad on/off (useful when an external mouse is connected).
Mod+Shift+X { spawn "bash" "-c" "~/.config/scripts/niri-toggle-touchpad.sh"; }
// Screen rotation shortcuts — delegate to unified-rotate.sh:
// Mod+Ctrl+E → clockwise (landscape → portrait right)
// Mod+Ctrl+D → counter-clockwise (landscape → portrait left)
Mod+Ctrl+E { spawn "bash" "-c" "~/.config/scripts/screenrotationwcw.sh"; }
Mod+Ctrl+D { spawn "bash" "-c" "~/.config/scripts/screenrotationacw.sh"; }
// ── Lock / Exit ───────────────────────────────────────────────────────────
// ── Lock / Session end ────────────────────────────────────────────────────
// Mod+O: lock the screen immediately with swaylock.
// -f: fork so niri isn't blocked waiting for the lock process.
Mod+O { spawn "swaylock" "-f"; }
// Mod+Shift+O: quit niri (terminates the entire Wayland session).
Mod+Shift+O { quit; }
// Mod+Ctrl+O: immediately power off the machine via systemctl.
Mod+Ctrl+O { spawn "systemctl" "poweroff"; }
// Mod+Alt+O: open the power/session menu (pwr-dmenu.sh via wofi).
// WHY separate from Mod+Ctrl+O: provides a confirmation UI before any
// destructive action.
Mod+Alt+O { spawn "bash" "-c" "~/.config/scripts/pwr-dmenu.sh"; }
// Emergency quit: a deliberately awkward 4-modifier chord so it's
// never triggered accidentally, but always reachable as a last resort.
Mod+Alt+Ctrl+Shift+End { quit; }
}

View File

@ -1,20 +1,80 @@
// ─────────────────────────────────────────────────────────────────────────────
// modules/environment.kdl — Environment variables and cursor settings
//
// PURPOSE: Sets environment variables that niri injects into every child
// process it spawns (applications, autostart commands, etc.).
// Also configures the hardware cursor appearance and auto-hide behaviour.
//
// NOTE: Variables set here apply to the compositor's process tree. They will
// NOT retroactively affect processes already running before niri started.
// ─────────────────────────────────────────────────────────────────────────────
// ── Environment Variables ─────────────────────────────────────────────────────
environment {
// GTK_THEME: Tell GTK 3/4 applications which theme to load.
// WHY "cyberqueer": ensures every GTK app adopts the custom dark+pink palette
// without requiring each app to be individually configured.
GTK_THEME "cyberqueer"
// XCURSOR_SIZE: Hardware cursor icon size in pixels.
// WHY 40: on a HiDPI display at 2× scale, 40 CSS-pixels = 80 physical pixels,
// which renders at a readable size without being distractingly large.
XCURSOR_SIZE "40"
// XCURSOR_THEME: Name of the XCursor theme for the pointer icon set.
// "Nordzy-cursors-lefthand" is the left-handed variant of the Nordzy cursor
// theme. WHY left-handed: the primary user is left-handed.
XCURSOR_THEME "Nordzy-cursors-lefthand"
// QT_QPA_PLATFORMTHEME: Instruct Qt apps to use the GTK 3 platform theme
// plugin, so Qt dialogs inherit GTK colours and font settings.
// WHY: without this, Qt apps use a default Fusion style that clashes with
// the cyberqueer colour palette.
QT_QPA_PLATFORMTHEME "gtk3"
// QT_WAYLAND_DISABLE_WINDOWDECORATION: Tell Qt to suppress client-side
// title bars on Wayland (rely on the compositor's server-side decorations).
// WHY: consistent with prefer-no-csd in config.kdl; prevents double borders.
QT_WAYLAND_DISABLE_WINDOWDECORATION "1"
// MOZ_ENABLE_WAYLAND: Force Firefox/Thunderbird to use the native Wayland
// backend instead of falling back to XWayland.
// WHY: native Wayland gives better performance, correct HiDPI scaling, and
// proper fractional scaling support.
MOZ_ENABLE_WAYLAND "1"
// ELECTRON_OZONE_PLATFORM_HINT: Hint Electron apps (VSCode, Discord, etc.)
// to auto-select the Wayland Ozone backend when running under Wayland.
// "auto" lets Electron decide; it chooses Wayland when $WAYLAND_DISPLAY is set.
ELECTRON_OZONE_PLATFORM_HINT "auto"
// NIXOS_OZONE_WL: Compatibility variable recognised by Electron apps packaged
// for NixOS to activate Wayland Ozone. Harmless on non-NixOS systems; included
// so Flatpak/AUR Electron packages that check this also opt into Wayland.
NIXOS_OZONE_WL "1"
// GDK_SCALE: Global integer scale factor for GTK 3/4 applications.
// WHY 2: matches the display's 2× HiDPI scale so UI elements are not blurry.
// Note: this is an integer scale; fractional scaling is handled separately
// by the Wayland output configuration in outputs.kdl.
GDK_SCALE "2"
}
// ── Cursor ────────────────────────────────────────────────────────────────────
cursor {
// xcursor-theme: compositor-level cursor theme (overrides the env var above
// for the pointer rendered by niri itself, not just child processes).
xcursor-theme "Nordzy-cursors-lefthand"
// xcursor-size: cursor size at compositor level, matching XCURSOR_SIZE above.
xcursor-size 40
// hide-after-inactive-ms: automatically hide the cursor after 5 seconds of
// no mouse movement. WHY: reduces visual clutter during reading or video.
hide-after-inactive-ms 5000
// hide-when-typing: immediately hide the cursor when a key is pressed.
// WHY: removes the cursor from the field of view while typing so it doesn't
// obscure text — restored as soon as the mouse moves again.
hide-when-typing
}

View File

@ -1,3 +1,12 @@
#!/usr/bin/env bash
# Encrypt a plaintext string with AES-256-CBC and output base64-encoded ciphertext.
# Usage: encrypt.sh <plaintext> <passphrase>
# The result can be decrypted with decrypt.sh using the same passphrase.
# -a : base64-encode the binary ciphertext so the result is printable/copy-pasteable
# -salt : prepend a random 8-byte salt to prevent identical plaintexts from
# producing identical outputs (thwarts rainbow-table attacks)
# -pbkdf2 : derive the key via PBKDF2 (Password-Based Key Derivation Function 2);
# more resistant to brute-force than the deprecated EVP_BytesToKey default
# -pass pass : read the passphrase from CLI argument $2 (avoids an interactive prompt)
echo $1 | openssl aes-256-cbc -a -salt -pbkdf2 -pass pass:$2

View File

@ -1,43 +1,56 @@
-- ── Bootstrap lazy.nvim ───────────────────────────────────────────────────────
-- lazy.nvim is the plugin manager. On first launch it clones itself into the
-- Neovim data directory (~/.local/share/nvim/lazy/lazy.nvim).
-- vim.uv is the preferred libuv binding in Neovim >= 0.10; vim.loop is the
-- legacy alias kept for compatibility with older releases.
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
vim.fn.system({
"git", "clone", "--filter=blob:none",
"git", "clone",
"--filter=blob:none", -- blobless clone: skips file blobs, fetching only on checkout
"https://github.com/folke/lazy.nvim.git",
"--branch=stable", lazypath,
})
end
-- Prepend lazy's path so Neovim can find and load it before any other plugin.
vim.opt.rtp:prepend(lazypath)
-- ── Plugins ───────────────────────────────────────────────────────────────────
require("lazy").setup({
-- CyberQueer colorscheme loaded from the local dotfiles tree (not from GitHub)
-- so that apply-theme.sh changes are picked up without a plugin update.
{ dir = vim.fn.expand("~/Dotfiles/nvim/theme/cyberqueer.nvim") },
"rktjmp/lush.nvim",
"tpope/vim-sensible",
"junegunn/goyo.vim",
"arecarn/vim-crunch",
"preservim/nerdtree",
"ryanoasis/vim-devicons",
"rktjmp/lush.nvim", -- HSL-based colorscheme builder used by cyberqueer
"tpope/vim-sensible", -- Sensible Vim defaults (better backspace, history, etc.)
"junegunn/goyo.vim", -- Distraction-free writing mode (centers and pads the buffer)
"arecarn/vim-crunch", -- Evaluate math expressions inside the buffer
"preservim/nerdtree", -- File-tree sidebar navigator
"ryanoasis/vim-devicons", -- Nerd Font icons for NERDTree and airline
-- fzf binary + Vim integration; build step runs fzf#install() to compile the binary
{ "junegunn/fzf", build = function() vim.fn["fzf#install"]() end },
"junegunn/fzf.vim",
"vim-airline/vim-airline",
"vim-airline/vim-airline-themes",
"voldikss/vim-floaterm",
"rust-lang/rust.vim",
"norcalli/nvim-colorizer.lua",
"junegunn/fzf.vim", -- :Files, :Rg, :Buffers etc. powered by fzf
"vim-airline/vim-airline", -- Status/tabline with Powerline-style separators
"vim-airline/vim-airline-themes", -- Theme library for vim-airline (cyberqueer theme used)
"voldikss/vim-floaterm", -- Floating terminal windows inside Neovim
"rust-lang/rust.vim", -- Rust syntax, fmt-on-save, and cargo integration
"norcalli/nvim-colorizer.lua", -- Inline hex/RGB color previews in CSS, config files, etc.
-- CoC: full LSP client with completion, hover, diagnostics; pinned to "release" branch
{ "neoclide/coc.nvim", branch = "release" },
-- vim-visual-multi: multiple-cursor editing (like Sublime Text ctrl+d)
{ "mg979/vim-visual-multi", branch = "master" },
"SirVer/ultisnips",
"honza/vim-snippets",
"mfussenegger/nvim-dap",
"elihunter173/dirbuf.nvim",
"tpope/vim-dadbod",
"kristijanhusak/vim-dadbod-ui",
"kristijanhusak/vim-dadbod-completion",
"nvim-mini/mini.icons",
"tadmccorkle/markdown.nvim",
"SirVer/ultisnips", -- Snippet engine (expand/jump triggers configured below)
"honza/vim-snippets", -- Community snippet library consumed by UltiSnips
"mfussenegger/nvim-dap", -- Debug Adapter Protocol client for step-through debugging
"elihunter173/dirbuf.nvim", -- Edit directory contents like a buffer (rename, delete, etc.)
"tpope/vim-dadbod", -- Database client (SQL queries against any DB URL)
"kristijanhusak/vim-dadbod-ui", -- TUI for vim-dadbod (tree browser + result panes)
"kristijanhusak/vim-dadbod-completion", -- CoC/native completion source for SQL via dadbod
"nvim-mini/mini.icons", -- Minimal icon provider used by dadbod-ui and others
"tadmccorkle/markdown.nvim", -- Markdown helpers: table formatting, checkboxes, etc.
-- glow.nvim: render Markdown in a floating window via the `glow` CLI
{ "ellisonleao/glow.nvim", config = true },
"itchyny/calendar.vim",
"itchyny/calendar.vim", -- Interactive calendar; used in the PIM overlay (key "g")
-- claude-code.nvim: Claude Code integration (opens/communicates with the `claude` CLI)
{
"greggh/claude-code.nvim",
dependencies = { "nvim-lua/plenary.nvim" },
@ -46,61 +59,75 @@ require("lazy").setup({
end,
},
}, {
-- keep lazy's own UI out of the way on first install
-- Use a bundled colorscheme during the initial lazy install so the UI is
-- readable even before cyberqueer is downloaded and compiled.
install = { colorscheme = { "habamax" } },
})
-- ── Colorscheme & UI ──────────────────────────────────────────────────────────
vim.cmd("colorscheme cyberqueer")
-- Tell airline to use Powerline-patched glyphs for segment separators.
vim.g.airline_powerline_fonts = 1
vim.g.airline_theme = "cyberqueer"
-- Embed machine identity in the airline status bar (section_x = right side).
-- Useful when SSHed into multiple machines or running nested Neovim sessions.
local ipaddr = vim.trim(vim.fn.system("hostname -i"))
local hostname = vim.trim(vim.fn.system("hostname -s"))
vim.g.airline_section_x = "IP:" .. ipaddr .. " DNS:" .. hostname
-- ── Providers ─────────────────────────────────────────────────────────────────
-- Disable unused remote plugin providers to suppress ":checkhealth" warnings
-- and avoid the startup penalty of probing for ruby/perl interpreters.
vim.g.loaded_ruby_provider = 0
vim.g.loaded_perl_provider = 0
-- ── Editor options ────────────────────────────────────────────────────────────
-- Enable filetype-specific plugins and indentation rules (e.g. Python 4-space).
vim.cmd("filetype plugin indent on")
vim.cmd("syntax enable")
vim.opt.number = true
vim.opt.relativenumber = true
vim.opt.cursorline = true
vim.opt.cursorcolumn = true
vim.opt.showmode = false
vim.opt.shiftwidth = 4
vim.opt.scrolloff = 5
vim.opt.wrap = false
vim.opt.incsearch = true
vim.opt.ignorecase = true
vim.opt.smartcase = true
vim.opt.showcmd = true
vim.opt.showmatch = true
vim.opt.hlsearch = true
vim.opt.history = 1000
vim.opt.wildmenu = true
vim.opt.wildmode = "list:longest"
vim.opt.number = true -- show absolute line numbers in the gutter
vim.opt.relativenumber = true -- show relative numbers above/below for fast j/k jumps
vim.opt.cursorline = true -- highlight the entire line the cursor is on
vim.opt.cursorcolumn = true -- highlight the entire column the cursor is in
vim.opt.showmode = false -- airline already shows mode; suppress the redundant "-- INSERT --" message
vim.opt.shiftwidth = 4 -- number of spaces for each indentation level (>> / <<)
vim.opt.scrolloff = 5 -- keep at least 5 lines visible above/below the cursor
vim.opt.wrap = false -- disable line wrapping (horizontal scroll instead)
vim.opt.incsearch = true -- show matches incrementally as you type the search pattern
vim.opt.ignorecase = true -- case-insensitive search by default
vim.opt.smartcase = true -- override ignorecase when the pattern contains uppercase
vim.opt.showcmd = true -- show the partial command being typed in the status line
vim.opt.showmatch = true -- briefly jump to the matching bracket when one is typed
vim.opt.hlsearch = true -- highlight all search matches (clear with :noh)
vim.opt.history = 1000 -- keep 1000 entries in command and search history
vim.opt.wildmenu = true -- show a completion menu for : commands
vim.opt.wildmode = "list:longest" -- first tab lists completions, second completes to longest common prefix
-- ── Keymaps ───────────────────────────────────────────────────────────────────
-- window navigation (normal mode)
-- Use Ctrl+hjkl to move between splits instead of the two-key <C-w> prefix.
-- <C-l> cycles to the next window (wraps); others jump directionally.
vim.keymap.set("n", "<C-l>", "<C-w>w")
vim.keymap.set("n", "<C-h>", "<C-w>h")
vim.keymap.set("n", "<C-j>", "<C-w>j")
vim.keymap.set("n", "<C-k>", "<C-w>k")
-- window navigation from terminal-insert mode (exit insert, then move)
-- <C-\><C-n> exits terminal-insert mode first; without it the control sequence
-- is sent to the shell process instead of being handled by Neovim.
vim.keymap.set("t", "<C-h>", "<C-\\><C-n><C-w>h", { silent = true })
vim.keymap.set("t", "<C-j>", "<C-\\><C-n><C-w>j", { silent = true })
vim.keymap.set("t", "<C-k>", "<C-\\><C-n><C-w>k", { silent = true })
vim.keymap.set("t", "<C-l>", "<C-\\><C-n><C-w>w", { silent = true })
-- auto-enter insert mode when focusing a terminal buffer (skip floaterm)
-- Without this, focusing a terminal buffer requires pressing 'i' manually.
-- Floaterm manages its own mode, so we exclude it to avoid conflicting with
-- its internal state machine.
vim.api.nvim_create_autocmd("BufEnter", {
pattern = "term://*",
callback = function()
@ -111,6 +138,8 @@ vim.api.nvim_create_autocmd("BufEnter", {
})
-- calendar.vim steals <C-hjkl> for month nav; restore window movement
-- The plugin sets buffer-local mappings on FileType calendar, so we override
-- them with { buffer = true } mappings of our own that fire after the plugin's.
vim.api.nvim_create_autocmd("FileType", {
pattern = "calendar",
callback = function()
@ -122,23 +151,31 @@ vim.api.nvim_create_autocmd("FileType", {
end,
})
-- quick actions
-- quick actions: single-key shortcuts for frequently used commands
-- t: open a new floating terminal via vim-floaterm
vim.keymap.set("n", "t", ":FloatermNew<CR>", { silent = true })
-- e: toggle the NERDTree sidebar; <C-W>l immediately moves focus back to the
-- editing window so the cursor doesn't land in the tree
vim.keymap.set("n", "e", ":NERDTreeToggle<CR><C-W>l", { silent = true })
-- s: toggle the vim-dadbod-ui database browser
vim.keymap.set("n", "s", ":DBUIToggle<CR>", { silent = true })
-- q: try to save-quit (wq); fall back to force-quit (q) for unsaved/read-only buffers
vim.keymap.set("n", "q", function()
local ok = pcall(vim.cmd, "wq")
if not ok then vim.cmd("q") end
end, { silent = true })
-- insert mode completion
-- insert mode completion: Tab triggers Neovim's built-in keyword completion (<C-N>)
-- S-Tab reverts to a literal Tab character so indentation still works normally.
vim.keymap.set("i", "<TAB>", "<C-N>")
vim.keymap.set("i", "<S-TAB>", "<TAB>")
-- sudo save
-- sudo save: :w!! pipes the buffer through sudo tee, bypassing a read-only
-- file system permission. The '%' is the current file path.
vim.cmd("ca w!! w !sudo tee '%'")
-- visual block shorthand
-- :Vb is a convenience alias for entering visual-block mode without pressing
-- Ctrl+v, which conflicts with paste in some terminal emulators.
vim.cmd("command! Vb normal! <C-v>")
-- ── UltiSnips ─────────────────────────────────────────────────────────────────

View File

@ -7,6 +7,8 @@
# Answerfile fields: drive, kernel, keymap, hostname, username, encrypt, fido2_root,
# fido2_user, run_tui (password always prompted interactively)
# -E: propagate ERR trap into functions and subshells; -e: abort on any error;
# -u: treat unset variables as errors; -o pipefail: catch failures inside pipes.
set -Eeuo pipefail
############################################
@ -20,22 +22,29 @@ LOGFILE="$HOME/arch-autoinstall.log"
echo "############################################"
echo
} >> "$LOGFILE"
# Redirect all subsequent stdout and stderr to both the terminal and the log.
# `tee -a` appends so repeated runs accumulate without overwriting; process
# substitution keeps the descriptor open for the full lifetime of the script.
exec > >(tee -a "$LOGFILE") 2>&1
############################################
# Error handler — TUI prompt to send log via croc
############################################
error_handler() {
# Capture exit code before any other command can overwrite $?.
# $LINENO is passed as $1 from the trap, so we default to '?' if missing.
local exit_code=$? line_num="${1:-?}"
echo "" >> "$LOGFILE"
echo "ERROR: installer failed at line $line_num (exit code $exit_code)" >> "$LOGFILE"
# Prefer a dialog TUI if available; fall back to plain readline on headless installs.
if command -v dialog &>/dev/null; then
if dialog --clear --title " Installer Error " \
--yesno \
"Installation failed at line $line_num (exit code: $exit_code).\n\nSend the log to another system via croc for analysis?" \
9 62; then
clear
# croc may not be present in the base live ISO — install it on demand.
if ! command -v croc &>/dev/null; then
echo "Installing croc..."
pacman -Sy --noconfirm croc 2>/dev/null || true
@ -53,6 +62,7 @@ error_handler() {
echo ""
echo "Installation failed at line $line_num (exit code $exit_code)."
read -rp "Send log via croc? [y/N]: " _croc_ans
# ${_croc_ans,,} lowercases the answer so "Y", "y" both match.
if [[ "${_croc_ans,,}" == "y" ]]; then
command -v croc &>/dev/null || pacman -Sy --noconfirm croc 2>/dev/null || true
croc send "$LOGFILE" || true
@ -62,17 +72,24 @@ error_handler() {
fi
exit "$exit_code"
}
# Fire error_handler on any ERR signal, passing the current line number.
# Single quotes prevent $LINENO from being evaluated at trap-definition time.
trap 'error_handler $LINENO' ERR
############################################
# ANSWERFILE
############################################
# Allow an external override via the environment; default to /answerfile.json
# which is where build.sh --preconf embeds the file in the live ISO.
ANSWERFILE="${ANSWERFILE:-/answerfile.json}"
AF_MODE=false
[[ -f "$ANSWERFILE" ]] && AF_MODE=true
af_get() {
# af_get <jq-filter> [default]
# Reads one field from the answerfile with a jq expression.
# `// empty` returns empty string instead of "null" when the field is absent;
# the outer `|| true` prevents a jq parse error from aborting the script.
local val
val=$(jq -r "${1} // empty" "$ANSWERFILE" 2>/dev/null || true)
if [[ -z "$val" ]]; then printf '%s' "${2:-}"; else printf '%s' "$val"; fi
@ -80,28 +97,38 @@ af_get() {
af_bool() {
# Returns YES or NO from a JSON boolean field
# `// false` provides a safe default when the key is absent entirely.
local val; val=$(jq -r "${1} // false" "$ANSWERFILE" 2>/dev/null || true)
[[ "$val" == "true" ]] && echo "YES" || echo "NO"
}
get_mac_suffix() {
# Produce a compact MAC address string (no colons) from the first non-loopback
# interface to create a unique per-machine hostname suffix at deploy time.
# The awk pattern skips "lo" by requiring the 3rd char to differ from 'o'.
local mac
mac=$(ip link show 2>/dev/null \
| awk '/^[0-9]+: [^l][^o]/{iface=1} iface && /link\/ether/{print $2; iface=0; exit}')
# Remove colons via parameter substitution: ${var//pattern/replacement}.
printf '%s' "${mac//:/}"
}
# Map drive paths to their correct partition suffix.
# NVMe (/dev/nvme0n1) and eMMC (/dev/mmcblk0) use a 'p' before the part number;
# conventional block devices (/dev/sda) do not.
part() { [[ "$1" == *nvme* || "$1" == *mmcblk* ]] && echo "${1}p${2}" || echo "${1}${2}"; }
if $AF_MODE; then
echo "Answerfile detected: $ANSWERFILE"
# Ensure jq is available
# jq is required to parse the answerfile; it may not be on the base live ISO.
command -v jq &>/dev/null || pacman -Sy --noconfirm jq
fi
############################################
# NETWORK CHECK
############################################
# Send one ICMP packet (-c1) with a 3-second timeout (-W3) to Cloudflare DNS.
# In AF_MODE we warn but continue — packages in pacstrap may already be cached.
if ! ping -c1 -W3 1.1.1.1 &>/dev/null; then
if $AF_MODE; then
echo "Warning: no internet connection detected — continuing in answerfile mode."
@ -123,6 +150,8 @@ fi
# KEYMAP
# To add more layouts: append "code|Display Name" to KEYMAPS
############################################
# Each entry is "keymap-code|Human-readable name"; the pipe acts as a cheap delimiter
# so both pieces of data live in a single array element without needing two arrays.
KEYMAPS=(
"us|English US"
"de|German"
@ -133,19 +162,26 @@ if $AF_MODE; then
else
echo "Select keyboard layout:"
for i in "${!KEYMAPS[@]}"; do
# %%|* strips everything from the first '|' onward → the keymap code.
_km_code="${KEYMAPS[$i]%%|*}"
# ##*| strips everything up to and including the last '|' → the display name.
_km_name="${KEYMAPS[$i]##*|}"
printf " %d) %-14s (%s)\n" $((i+1)) "$_km_name" "$_km_code"
done
read -rp "Choice [1]: " _KM_IDX
# Convert 1-based user input to 0-based array index; default to 1 if blank.
_KM_IDX=$(( ${_KM_IDX:-1} - 1 ))
if (( _KM_IDX >= 0 && _KM_IDX < ${#KEYMAPS[@]} )); then
LIVE_KEYMAP="${KEYMAPS[$_KM_IDX]%%|*}"
else
# Out-of-range input silently falls back to the first entry (us).
LIVE_KEYMAP="${KEYMAPS[0]%%|*}"
fi
fi
# Apply the chosen layout to the live environment immediately so subsequent
# interactive prompts can be typed with the correct physical keyboard.
loadkeys "$LIVE_KEYMAP"
# Also export as KEYMAP so the chroot heredoc can write /etc/vconsole.conf.
KEYMAP="$LIVE_KEYMAP"
############################################
@ -153,22 +189,27 @@ KEYMAP="$LIVE_KEYMAP"
############################################
if $AF_MODE; then
echo "WARNING: Automated install — all data on $(af_get '.drive' '/dev/?') will be ERASED."
# Give the operator a 5-second window to abort with Ctrl-C before destructive ops begin.
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
# Require the exact string "YES" (case-sensitive) to prevent accidental wipes.
[[ "$confirm" == "YES" ]] || { echo "Aborted."; exit 1; }
fi
############################################
# REQUIRED PACKAGES FOR INSTALL ENVIRONMENT
############################################
# Install tools needed by this script into the live environment before partitioning:
# parted — disk partitioning; cryptsetup — LUKS; libfido2/pam-u2f — FIDO2 enrollment.
pacman -Sy --noconfirm parted cryptsetup libfido2 pam-u2f
############################################
# DRIVE SELECTION
############################################
# Print current block device layout so the operator can confirm the target drive.
lsblk
if $AF_MODE && [[ -n "$(af_get '.drive')" ]]; then
DRIVE=$(af_get '.drive')
@ -183,6 +224,8 @@ fi
if $AF_MODE; then
KERNEL=$(af_get '.kernel' 'linux')
RAW_HOSTNAME=$(af_get '.hostname' '')
# Append MAC suffix to make the hostname unique when the same answerfile is
# deployed to multiple machines (e.g. a lab rollout or reinstall fleet).
if [[ -n "$RAW_HOSTNAME" ]]; then
HOSTNAME="${RAW_HOSTNAME}-$(get_mac_suffix)"
else
@ -202,6 +245,8 @@ else
read -rp "Enter hostname: " HOSTNAME
read -rp "Enter username: " USERNAME
read -rp "Enable disk encryption? (YES/NO): " ENCRYPT_DISK
# Default FIDO2 root to NO; only ask when encryption is active because FIDO2
# unlocking only makes sense on an encrypted root partition.
FIDO_ROOT="NO"
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
read -rp "Enable FIDO2 unlocking for root partition? (YES/NO): " FIDO_ROOT
@ -213,8 +258,11 @@ fi
read -rp "Enter password for $USERNAME: " USERPASS
[[ -z "$USERPASS" ]] && { echo "Error: password cannot be empty."; exit 1; }
# In interactive mode, decide now whether to run the dotfiles TUI inside chroot.
# AF_MODE already has RUN_TUI set from the answerfile.
if ! $AF_MODE; then
read -rp "Run dotfiles TUI setup inside chroot now? [YES/no]: " _RUN_TUI_IN
# Default to YES if the user presses Enter without typing anything.
_RUN_TUI_IN="${_RUN_TUI_IN:-YES}"
[[ "${_RUN_TUI_IN^^}" == "YES" ]] && RUN_TUI="YES" || RUN_TUI="NO"
fi
@ -222,12 +270,17 @@ fi
############################################
# RAM / PARTITION SIZING
############################################
# Read installed RAM in GiB; used to size both the swap and to calculate remaining
# root space. `--giga` prints in GiB units; awk extracts the total column.
RAM_GB=$(free --giga | awk '/^Mem:/ {print $2}')
# 15 GiB boot partition: large enough for multiple kernels, initramfs, and UKIs.
BOOT_SIZE=15GiB
SWAP_SIZE="${RAM_GB}GiB"
# lsblk -b reports raw bytes; divide down to GiB for integer arithmetic.
DISK_SIZE=$(lsblk -b -dn -o SIZE "$DRIVE")
DISK_GIB=$((DISK_SIZE / 1024 / 1024 / 1024))
# Root gets whatever is left after reserving 15 GiB for boot and RAM_GB for swap.
ROOT_GIB=$((DISK_GIB - RAM_GB - 15))
if (( ROOT_GIB < 8 )); then
echo "ERROR: Not enough disk space. Root would be only ${ROOT_GIB}GiB (need ≥8GiB)."
@ -242,12 +295,19 @@ echo " Swap: ${SWAP_SIZE}"
############################################
# PARTITION DISK
############################################
# --script suppresses interactive prompts; mklabel gpt wipes any existing table.
# Partition 1: ESP at 1MiB15GiB, flagged boot/esp so firmware can find it.
# Partition 2: ROOT immediately after the ESP.
# Partition 3: SWAP fills the rest (100%).
# Starting at 1MiB aligns the first partition to a 1 MiB boundary, which is
# optimal for both SSD erase blocks and spinner track alignment.
parted "$DRIVE" --script mklabel gpt \
mkpart ESP fat32 1MiB 15GiB \
set 1 boot on \
mkpart ROOT 15GiB "$((15 + ROOT_GIB))"GiB \
mkpart SWAP "$((15 + ROOT_GIB))"GiB 100%
# Resolve partition device paths via the `part` helper (handles NVMe 'p' suffix).
BOOT_PART=$(part "$DRIVE" 1)
ROOT_PART=$(part "$DRIVE" 2)
SWAP_PART=$(part "$DRIVE" 3)
@ -255,7 +315,9 @@ SWAP_PART=$(part "$DRIVE" 3)
############################################
# FORMAT BOOT + SWAP
############################################
# FAT32 is required for the EFI System Partition; -F32 sets the FAT variant.
mkfs.fat -F32 "$BOOT_PART"
# mkswap writes the swap header; swapon activates it for use by pacstrap.
mkswap "$SWAP_PART"
swapon "$SWAP_PART"
@ -266,7 +328,10 @@ LUKS_BACKUP_KEY="" # path to key file, set only when encryption is active
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
echo "Encrypting root partition..."
# -v (verbose) shows progress; luksFormat initialises a LUKS1 container by default
# and prompts for the primary passphrase interactively.
cryptsetup -v luksFormat "$ROOT_PART"
# Open the container and expose it as /dev/mapper/cryptroot for formatting.
cryptsetup open "$ROOT_PART" cryptroot
# ── Auto-generate backup LUKS key ──────────────────────────────────────────
@ -274,25 +339,38 @@ if [[ "$ENCRYPT_DISK" == "YES" ]]; then
# 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)
# Read 64 bytes from /dev/urandom and base64-encode them (-w0 disables line
# wrapping) to produce a printable key with high entropy.
dd if=/dev/urandom bs=64 count=1 2>/dev/null | base64 -w0 > "$LUKS_BACKUP_KEY"
echo "Enrolling auto-generated backup LUKS key..."
# luksAddKey reads the existing passphrase interactively to authorise adding
# the new key file into a free LUKS slot.
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..."
# --fido2-device=auto finds any connected FIDO2 token automatically.
# --fido2-with-client-pin=no skips PIN verification; the hardware
# user-presence tap alone is sufficient for this setup.
systemd-cryptenroll "$ROOT_PART" --fido2-device=auto --fido2-with-client-pin=no
fi
############################################
# BTRFS ON ENCRYPTED ROOT
############################################
# Format the decrypted mapper device, not the raw partition.
mkfs.btrfs /dev/mapper/cryptroot
# Mount flat (no subvolume) first so we can create the subvolume layout.
mount /dev/mapper/cryptroot /mnt
# @ is the conventional name for the root subvolume in btrfs-on-Arch setups;
# @home separates user data so it can be snapshotted or rolled back independently.
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home
# Unmount the flat view before remounting through the subvolumes.
umount /mnt
# Mount each subvolume at its final path for pacstrap to populate.
mount -o subvol=@ /dev/mapper/cryptroot /mnt
mkdir -p /mnt/home
mount -o subvol=@home /dev/mapper/cryptroot /mnt/home
@ -303,6 +381,7 @@ else
############################################
# BTRFS ON UNENCRYPTED ROOT
############################################
# Same subvolume layout as the encrypted branch for consistency.
mkfs.btrfs "$ROOT_PART"
mount "$ROOT_PART" /mnt
btrfs subvolume create /mnt/@
@ -315,11 +394,15 @@ else
fi
mkdir -p /mnt/boot
# Mount the ESP so GRUB and the kernel can be installed inside the new root tree.
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` creates the destination file with mode 0400 (root read-only)
# in one atomic step, avoiding an intermediate world-readable state.
install -m 400 "$LUKS_BACKUP_KEY" /mnt/_LUKS_BACKUP_KEY
# Remove the temp file from the live environment immediately after copying.
rm -f "$LUKS_BACKUP_KEY"
echo "Backup LUKS key written to /_LUKS_BACKUP_KEY in new system."
fi
@ -327,11 +410,14 @@ fi
############################################
# GPU DETECTION
############################################
# lspci lists PCI devices; filter for VGA and 3D controllers to identify the GPU.
# `|| true` prevents set -e from aborting if no GPU line is found.
GPU_INFO=$(lspci | grep -E "VGA|3D" || true)
GPU_PKGS=""
if echo "$GPU_INFO" | grep -qi "NVIDIA"; then
GPU_PKGS="nvidia nvidia-utils"
elif echo "$GPU_INFO" | grep -qi "AMD"; then
# xf86-video-amdgpu provides the Xorg DDX driver; the kernel DRM driver ships in linux-firmware.
GPU_PKGS="xf86-video-amdgpu"
elif echo "$GPU_INFO" | grep -qi "Intel"; then
GPU_PKGS="xf86-video-intel"
@ -341,6 +427,8 @@ echo "Detected GPU: ${GPU_INFO:-none}"
############################################
# BASE INSTALL
############################################
# pacstrap installs packages into /mnt using pacman's keyring (-K initialises it).
# GPU_PKGS is intentionally unquoted so it expands to multiple words (or nothing).
# shellcheck disable=SC2086
pacstrap -K /mnt base base-devel "$KERNEL" linux-firmware vim bash zsh git less btop fastfetch \
networkmanager grub cryptsetup libfido2 pam-u2f efibootmgr sudo btrfs-progs lvm2 jq $GPU_PKGS
@ -348,11 +436,15 @@ pacstrap -K /mnt base base-devel "$KERNEL" linux-firmware vim bash zsh git less
############################################
# FSTAB
############################################
# genfstab -U uses UUIDs (rather than device names or labels) so the fstab
# remains valid even if drive letter assignments change after a hardware swap.
genfstab -U /mnt >> /mnt/etc/fstab
############################################
# COPY ANSWERFILE INTO NEW SYSTEM
############################################
# Make the answerfile available inside the new system so the dotfiles TUI
# installer can read the same selections without re-prompting the user.
if $AF_MODE; then
install -m 644 "$ANSWERFILE" /mnt/answerfile.json
fi
@ -360,48 +452,69 @@ fi
############################################
# PASS VARIABLES INTO CHROOT
############################################
# Variables must be exported so the arch-chroot heredoc inherits them.
# The heredoc uses single-quote delimiter ('CHROOT_EOF') which prevents expansion
# outside the chroot, so env vars must be in the environment, not inline.
export HOSTNAME USERNAME USERPASS ROOT_PART KERNEL FIDO_ROOT FIDO_USER ENCRYPT_DISK KEYMAP
############################################
# CHROOT CONFIGURATION
############################################
# arch-chroot binds /proc, /sys, /dev and runs commands inside the new root.
# Single-quoted 'CHROOT_EOF' prevents the outer shell from expanding variables;
# the inner shell resolves them from the exported environment above.
arch-chroot /mnt /bin/bash <<'CHROOT_EOF'
set -euo pipefail
# Locale
# Uncomment (create) the en_US.UTF-8 entry so locale-gen can compile it.
echo "en_US.UTF-8 UTF-8" > /etc/locale.gen
locale-gen
echo "LANG=en_US.UTF-8" > /etc/locale.conf
# vconsole.conf sets the keymap for the virtual console (TTY) on every boot.
echo "KEYMAP=${KEYMAP}" > /etc/vconsole.conf
# Time / hostname
# Symlink the timezone file so /etc/localtime always points at the correct zoneinfo.
ln -sf /usr/share/zoneinfo/Europe/Vienna /etc/localtime
# --systohc writes the current system time to the hardware clock (RTC).
hwclock --systohc
echo "$HOSTNAME" > /etc/hostname
# NetworkManager
# Enable at boot so the system has networking on first login without manual setup.
systemctl enable NetworkManager
# Populate /etc/skel with dotfiles and base configs for all new users
# Populate /etc/skel before user creation so useradd -m copies everything
echo "Cloning dotfiles into /etc/skel..."
git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git /etc/skel/Dotfiles \
|| echo "Warning: dotfiles clone into skel failed — will fall back to direct clone."
# Seed /etc/skel with base shell dotfiles from the repo clone
if [[ -d /etc/skel/Dotfiles ]]; then
D=/etc/skel/Dotfiles
# Copy individually; `2>/dev/null || true` silently skips any file that is absent
# in this repo revision rather than aborting the install.
cp "$D/.zshrc" /etc/skel/.zshrc 2>/dev/null || true
cp "$D/.bashrc" /etc/skel/.bashrc 2>/dev/null || true
cp "$D/.vimrc" /etc/skel/.vimrc 2>/dev/null || true
fi
# User
# -m: create home dir; -G wheel: add to sudoers group; -s /bin/zsh: default shell.
useradd -m -G wheel -s /bin/zsh "$USERNAME"
# chpasswd reads "user:pass" from stdin to set the password non-interactively.
echo "$USERNAME:$USERPASS" | chpasswd
# Grant wheel group full sudo access (ALL:ALL covers any user/group runas context).
echo "%wheel ALL=(ALL:ALL) ALL" >> /etc/sudoers
###################################################
# INITRAMFS CONFIG
###################################################
# The HOOKS line controls which initramfs modules are compiled in.
# Three variants depending on encryption and FIDO2 choices:
# 1. FIDO2 root unlock: needs `systemd` + `sd-encrypt` for systemd-cryptsetup.
# 2. Password-only LUKS: uses classic `encrypt` hook (no systemd dependency).
# 3. Unencrypted: minimal hook set — no encrypt hook needed.
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
@ -410,25 +523,35 @@ else
sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect microcode modconf kms consolefont block btrfs filesystems keyboard fsck)/' /etc/mkinitcpio.conf
fi
# Regenerate the initramfs for the selected kernel preset.
mkinitcpio -p "$KERNEL"
###################################################
# GRUB CONFIG
###################################################
# Read the raw partition UUID; used in kernel cmdline for stable device identification.
UUID=$(blkid -s UUID -o value "$ROOT_PART")
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
if [[ "$FIDO_ROOT" == "YES" ]]; then
# systemd-cryptsetup syntax: rd.luks.name=<UUID>=<name>.
# The FIDO2 device is auto-detected by systemd-cryptenroll at boot.
KERNEL_CMD="rd.luks.name=${UUID}=cryptroot root=/dev/mapper/cryptroot"
else
# Classic initramfs encrypt hook syntax: cryptdevice=UUID=<UUID>:<name>.
KERNEL_CMD="cryptdevice=UUID=${UUID}:cryptroot root=/dev/mapper/cryptroot"
fi
else
# Unencrypted btrfs: root= by UUID and rootflags to select the @ subvolume.
KERNEL_CMD="root=UUID=${UUID} rootflags=subvol=@"
fi
# Inject the kernel command line into /etc/default/grub using sed.
# The `|` delimiter avoids conflicts with `/` in device paths.
sed -i "s|^GRUB_CMDLINE_LINUX=.*|GRUB_CMDLINE_LINUX=\"$KERNEL_CMD\"|" /etc/default/grub
# Install GRUB to the EFI partition; --bootloader-id sets the NVRAM entry name.
grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=M-Archy-GRUB
# Generate grub.cfg from /etc/default/grub and detected kernels.
grub-mkconfig -o /boot/grub/grub.cfg
###################################################
@ -437,23 +560,23 @@ grub-mkconfig -o /boot/grub/grub.cfg
if [[ "$FIDO_USER" == "YES" ]]; then
mkdir -p "/home/$USERNAME/.config/Yubico"
echo "Insert FIDO2 key for user login and touch when prompted..."
# pamu2fcfg registers the FIDO2 key and writes the credential to u2f_keys.
# Running under sudo -u ensures the file is generated with the correct UID context.
sudo -u "$USERNAME" pamu2fcfg -u "$USERNAME" > "/home/$USERNAME/.config/Yubico/u2f_keys"
chown "$USERNAME":"$USERNAME" "/home/$USERNAME/.config/Yubico/u2f_keys"
# Append the PAM rule so pam_u2f is required for system-local-login sessions.
echo "auth required pam_u2f.so" >> /etc/pam.d/system-local-login
fi
###################################################
# CLONE DOTFILES
###################################################
if [[ -d "/home/$USERNAME/Dotfiles" ]]; then
echo "Dotfiles already in home via skel — fixing ownership."
chown -R "$USERNAME:$USERNAME" "/home/$USERNAME"
else
echo "Cloning dotfiles directly to user home (skel clone failed)..."
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."
fi
# Fall back to a direct clone if /etc/skel/Dotfiles was not copied into home
# (happens when the skel clone failed or useradd did not copy skel).
echo "Cloning 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."
CHROOT_EOF
@ -461,14 +584,20 @@ CHROOT_EOF
# DOTFILES TUI SETUP (in-chroot, optional)
############################################
if [[ "${RUN_TUI^^}" == "YES" ]]; then
# Grant passwordless sudo temporarily so the TUI installer can call pacman/yay
# without needing a password inside the chroot (the real sudoers is already set).
# The file is removed immediately after the TUI exits.
echo "${USERNAME} ALL=(ALL) NOPASSWD: ALL" \
| arch-chroot /mnt tee /etc/sudoers.d/99-setup-nopasswd > /dev/null
echo "Running tui-install.sh as ${USERNAME} inside chroot..."
# `runuser -u` switches to the unprivileged user inside the chroot so that
# AUR helpers and dotfiles are owned/built by the correct UID.
arch-chroot /mnt runuser -u "${USERNAME}" -- \
bash "/home/${USERNAME}/Dotfiles/setup/tui-install.sh" \
|| echo "Warning: tui-install exited with errors — check ~/dotfiles-install.log in the new system."
# Remove the temporary no-password sudoers drop-in after setup is done.
arch-chroot /mnt rm -f /etc/sudoers.d/99-setup-nopasswd
fi
@ -507,6 +636,8 @@ echo "Log file: $LOGFILE"
echo "############################################"
echo
# Copy install log into /boot so it survives and is accessible before login.
# `2>/dev/null || true` silently ignores failures (e.g. /mnt/boot not writable).
cp "$LOGFILE" /mnt/boot/ 2>/dev/null || true
echo "Installation complete! Unmount and reboot:"

View File

@ -4,6 +4,8 @@
# If /answerfile.json exists (e.g. embedded via build.sh --preconf), all prompts
# are answered from it. Missing fields fall back to interactive prompts.
# -E: propagate ERR trap into functions/subshells; -e: abort on error;
# -u: treat unset variables as errors; -o pipefail: fail on pipe errors.
set -Eeuo pipefail
############################################
@ -17,12 +19,17 @@ LOGFILE="$HOME/archbaseos-guided-install.log"
echo "############################################"
echo
} >> "$LOGFILE"
# Redirect all subsequent stdout/stderr to both the terminal and the log file.
# `tee -a` appends so repeated runs accumulate without overwriting; process
# substitution keeps the descriptor open for the full lifetime of the script.
exec > >(tee -a "$LOGFILE") 2>&1
############################################
# Error handler — TUI prompt to send log via croc
############################################
error_handler() {
# Capture exit code before any other command can overwrite $?.
# $LINENO is passed as $1 from the trap; default to '?' if absent.
local exit_code=$? line_num="${1:-?}"
echo "" >> "$LOGFILE"
echo "ERROR: installer failed at line $line_num (exit code $exit_code)" >> "$LOGFILE"
@ -30,7 +37,9 @@ error_handler() {
echo ""
echo "Installation failed at line $line_num (exit code $exit_code)."
read -rp "Send log via croc? [y/N]: " _croc_ans
# ${_croc_ans,,} lowercases the answer so "Y" and "y" both match.
if [[ "${_croc_ans,,}" == "y" ]]; then
# Install croc on demand — it may not be present in the base live ISO.
command -v croc &>/dev/null || pacman -Sy --noconfirm croc 2>/dev/null || true
croc send "$LOGFILE" || true
else
@ -38,6 +47,8 @@ error_handler() {
fi
exit "$exit_code"
}
# Fire error_handler on any ERR signal, passing the current line number.
# Single quotes prevent $LINENO from being evaluated at trap-definition time.
trap 'error_handler $LINENO' ERR
############################################
@ -45,12 +56,14 @@ trap 'error_handler $LINENO' ERR
############################################
confirm() {
# Require the exact string "YES" before erasing $1 — prevents accidental wipes.
echo "WARNING: This will ERASE ALL DATA on $1"
read -rp "Type YES to continue: " ans
[[ $ans == "YES" ]]
}
ask() {
# Thin wrapper around read -rp so callers can capture the result via $().
local prompt=$1
local var
read -rp "$prompt: " var
@ -58,6 +71,8 @@ ask() {
}
pause() {
# Used before interactive hardware steps (e.g. FIDO2 enrollment) to give the
# operator time to insert the key before the script continues.
read -rp "Press ENTER to continue..."
}
@ -68,30 +83,41 @@ part() { [[ "$1" == *nvme* || "$1" == *mmcblk* ]] && echo "${1}p${2}" || echo "$
############################################
# ANSWERFILE
############################################
# Allow an external override via the environment; default to /answerfile.json,
# the path where build.sh --preconf embeds the file in the live ISO.
ANSWERFILE="${ANSWERFILE:-/answerfile.json}"
AF_MODE=false
[[ -f "$ANSWERFILE" ]] && AF_MODE=true
af_get() {
# Reads one field from the answerfile using a jq filter expression.
# `// empty` returns an empty string instead of "null" when the field is absent;
# `|| true` prevents a jq parse error from aborting the script via set -e.
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() {
# `// false` provides a safe default so missing boolean keys don't cause errors.
local val; val=$(jq -r "${1} // false" "$ANSWERFILE" 2>/dev/null || true)
[[ "$val" == "true" ]] && echo "YES" || echo "NO"
}
get_mac_suffix() {
# Produce a compact MAC address (no colons) from the first non-loopback interface
# to create a unique per-machine hostname suffix during unattended deployment.
# The awk pattern skips "lo" by requiring the 3rd character to differ from 'o'.
local mac
mac=$(ip link show 2>/dev/null \
| awk '/^[0-9]+: [^l][^o]/{iface=1} iface && /link\/ether/{print $2; iface=0; exit}')
# Remove colons via bash parameter substitution: ${var//pattern/replacement}.
printf '%s' "${mac//:/}"
}
if $AF_MODE; then
echo "== Arch Linux Guided Installer (answerfile mode) =="
# jq is required to parse the answerfile; it may not ship in the base live ISO.
command -v jq &>/dev/null || pacman -Sy --noconfirm jq
else
echo "== Arch Linux FIDO2-Ready Installer =="
@ -100,6 +126,8 @@ fi
############################################
# NETWORK CHECK
############################################
# Send one ICMP packet (-c1) with a 3-second timeout (-W3) to Cloudflare DNS.
# In AF_MODE we warn but continue — packages may already be cached in the live env.
if ! ping -c1 -W3 1.1.1.1 &>/dev/null; then
if $AF_MODE; then
echo "Warning: no internet connection detected — continuing in answerfile mode."
@ -121,6 +149,8 @@ fi
# Begin
############################################
# Each entry is "keymap-code|Human-readable name"; the pipe delimiter lets both
# pieces of data live in a single array element without needing two parallel arrays.
KEYMAPS=(
"us|English US"
"de|German"
@ -132,6 +162,8 @@ KEYMAPS=(
if $AF_MODE; then
KERNEL=$(af_get '.kernel' 'linux')
RAW_HOSTNAME=$(af_get '.hostname' '')
# Append MAC suffix to make the hostname unique across machines when the same
# answerfile is deployed to a fleet (lab rollout, reinstall, etc.).
if [[ -n "$RAW_HOSTNAME" ]]; then
HOSTNAME="${RAW_HOSTNAME}-$(get_mac_suffix)"
else
@ -151,6 +183,8 @@ else
HOSTNAME=$(ask "Hostname")
USERNAME=$(ask "Username")
read -rp "Enable disk encryption? (YES/NO): " ENCRYPT_DISK
# Default FIDO2 root to NO; only ask when encryption is active because FIDO2
# unlocking only makes sense on an encrypted partition.
ENABLE_FIDO_ROOT="NO"
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
read -rp "Enable FIDO2 for unlocking root? (YES/NO): " ENABLE_FIDO_ROOT
@ -160,44 +194,56 @@ else
echo ""
echo "Select keyboard layout for installed system:"
for i in "${!KEYMAPS[@]}"; do
# %%|* strips from the first '|' onward → keymap code.
_km_code="${KEYMAPS[$i]%%|*}"
# ##*| strips up to and including the last '|' → display name.
_km_name="${KEYMAPS[$i]##*|}"
printf " %d) %-14s (%s)\n" $((i+1)) "$_km_name" "$_km_code"
done
read -rp "Choice [1]: " _KM_CHOICE
# Convert 1-based user input to 0-based array index; default to 1 if blank.
_KM_CHOICE=$(( ${_KM_CHOICE:-1} - 1 ))
if (( _KM_CHOICE >= 0 && _KM_CHOICE < ${#KEYMAPS[@]} )); then
KEYMAP="${KEYMAPS[$_KM_CHOICE]%%|*}"
else
# Out-of-range input silently falls back to the first entry (us).
KEYMAP="${KEYMAPS[0]%%|*}"
fi
fi
# Password always prompted interactively — never stored in the answerfile.
read -rp "Password for $USERNAME: " USERPASS
[[ -z "$USERPASS" ]] && { echo "Error: password cannot be empty."; exit 1; }
# Print block device layout so the operator can visually confirm the target drive.
lsblk
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..."
# Give the operator a 5-second window to abort with Ctrl-C before destructive ops.
sleep 5
else
DRIVE=$(ask "Enter install drive (e.g., /dev/sda)")
confirm "$DRIVE" || exit 1
fi
# Required packages
# Required packages — installed into the live environment before partitioning.
# -d skips full dependency resolution for speed (these are standalone tools).
# systemd-ukify included for Unified Kernel Image support if needed post-install.
pacman -Syd --noconfirm parted cryptsetup libfido2 pam-u2f systemd-ukify jq
############################################
# Partitioning
############################################
# Read installed RAM in GiB for swap sizing; awk extracts the total-memory column.
RAM_GB=$(free --giga | awk '/Mem/ {print $2}')
# lsblk -b: bytes; -d: disk only (no partitions); -n: no header; -o SIZE: size column.
DISK_GB=$(lsblk -dn -o SIZE -b "$DRIVE" | awk '{print int($1/1024/1024/1024)}')
# Reserve 5 GiB for the EFI partition and 1 GiB for alignment/overhead.
EFI_SIZE=5
SWAP_SIZE=$RAM_GB
ROOT_SIZE=$((DISK_GB - SWAP_SIZE - EFI_SIZE - 1))
@ -209,48 +255,71 @@ fi
echo "EFI=${EFI_SIZE}G, Root=${ROOT_SIZE}G, Swap=${SWAP_SIZE}G"
# -s: script mode (no interactive prompts); GPT is required for UEFI booting.
# `set 1 esp on` marks partition 1 as the EFI System Partition.
# Starting at 1MiB aligns to SSD erase-block / spinner track boundaries.
# The last partition uses 100% to consume all remaining space.
parted -s "$DRIVE" mklabel gpt \
mkpart EFI fat32 1MiB "${EFI_SIZE}GiB" \
set 1 esp on \
mkpart ROOT "${EFI_SIZE}GiB" "$((EFI_SIZE + ROOT_SIZE))GiB" \
mkpart SWAP "$((EFI_SIZE + ROOT_SIZE))GiB" 100%
# Resolve partition paths via `part()` which inserts the NVMe/eMMC 'p' suffix.
EFI_PART=$(part "$DRIVE" 1)
ROOT_PART=$(part "$DRIVE" 2)
SWAP_PART=$(part "$DRIVE" 3)
# FAT32 is mandated by the UEFI specification for the EFI System Partition.
mkfs.fat -F32 "$EFI_PART"
# mkswap writes the swap header; swapon activates it immediately for use during install.
mkswap "$SWAP_PART"
swapon "$SWAP_PART"
############################################
# Encryption (optional)
############################################
# Initialise to empty; gets set to a temp file path only when encryption is active.
LUKS_BACKUP_KEY=""
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
echo "Formatting LUKS2 root..."
# --type luks2 uses the newer LUKS2 format required by systemd-cryptenroll for FIDO2.
cryptsetup luksFormat "$ROOT_PART" --type luks2
# Open (decrypt) the container; exposes it as /dev/mapper/cryptroot for formatting.
cryptsetup open "$ROOT_PART" cryptroot
# ── Auto-generate backup LUKS key ─────────────────────────────────────────
# A random key enrolled in a second LUKS slot enables recovery without
# the primary passphrase. Stored inside the encrypted volume so it is
# only accessible when the system is unlocked.
LUKS_BACKUP_KEY=$(mktemp /tmp/luks-backup-key.XXXXXX)
# Read 64 bytes of entropy; base64-encode with -w0 to disable line wrapping.
dd if=/dev/urandom bs=64 count=1 2>/dev/null | base64 -w0 > "$LUKS_BACKUP_KEY"
echo "Enrolling auto-generated backup LUKS key..."
# luksAddKey prompts for the existing passphrase to authorise adding the new key file.
cryptsetup luksAddKey "$ROOT_PART" "$LUKS_BACKUP_KEY"
if [[ "$ENABLE_FIDO_ROOT" == "YES" ]]; then
echo "Enroll FIDO2 key for LUKS2"
# pause() gives the operator time to insert the FIDO2 hardware key before enrollment.
pause
# systemd-cryptenroll writes the FIDO2 credential into a LUKS2 token slot.
# --fido2-with-client-pin=no: presence tap only, no PIN required at boot.
systemd-cryptenroll "$ROOT_PART" --fido2-device=auto --fido2-with-client-pin=no
fi
# Format btrfs on the decrypted mapper device, not the raw partition.
mkfs.btrfs /dev/mapper/cryptroot
# Mount flat (no subvolume) first so the subvolume tree can be created.
mount /dev/mapper/cryptroot /mnt
# @ is the conventional root subvolume; @home separates user data for independent snapshots.
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home
# Unmount the flat view before remounting through named subvolumes.
umount /mnt
# Remount each subvolume at its intended mountpoint for pacstrap to populate.
mount -o subvol=@ /dev/mapper/cryptroot /mnt
mkdir -p /mnt/home
mount -o subvol=@home /dev/mapper/cryptroot /mnt/home
@ -258,6 +327,7 @@ if [[ "$ENCRYPT_DISK" == "YES" ]]; then
else
echo "Skipping encryption — formatting root directly."
# Same btrfs subvolume layout as the encrypted path for consistency.
mkfs.btrfs "$ROOT_PART"
mount "$ROOT_PART" /mnt
btrfs subvolume create /mnt/@
@ -270,11 +340,15 @@ else
fi
mkdir -p /mnt/boot
# Mount the EFI partition at /mnt/boot so GRUB and the kernel install inside the new tree.
mount "$EFI_PART" /mnt/boot
# Place backup key inside the new system (readable only by root, inside LUKS container)
if [[ -n "$LUKS_BACKUP_KEY" ]]; then
# `install -m 400` creates the destination with mode 0400 (root read-only) atomically,
# avoiding an intermediate world-readable state from a separate chmod.
install -m 400 "$LUKS_BACKUP_KEY" /mnt/_LUKS_BACKUP_KEY
# Remove the temp file from the live environment immediately after copying.
rm -f "$LUKS_BACKUP_KEY"
echo "Backup LUKS key written to /_LUKS_BACKUP_KEY in new system."
fi
@ -282,10 +356,13 @@ fi
############################################
# Base System Install
############################################
# lspci lists PCI devices; grep for VGA and 3D controller lines to identify the GPU.
# `|| true` prevents set -e from aborting when no matching line is found.
GPU_INFO=$(lspci | grep -E "VGA|3D" || true)
GPU_PKGS=""
if echo "$GPU_INFO" | grep -qi nvidia; then
# nvidia-open is the open-source kernel module (Pascal+); preferred over nvidia-dkms.
GPU_PKGS="nvidia-open"
elif echo "$GPU_INFO" | grep -qi amd; then
GPU_PKGS="xf86-video-amdgpu"
@ -293,16 +370,21 @@ elif echo "$GPU_INFO" | grep -qi intel; then
GPU_PKGS="xf86-video-intel"
fi
# GPU_PKGS is intentionally unquoted so it word-splits into individual package names
# (or expands to nothing when no GPU was detected).
# 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 jq $GPU_PKGS
# -U: use UUIDs rather than device names so fstab entries survive hardware changes.
genfstab -U /mnt >> /mnt/etc/fstab
############################################
# COPY ANSWERFILE INTO NEW SYSTEM
############################################
# Make the answerfile available inside the new system so the dotfiles TUI installer
# can read the same selections without re-prompting.
if $AF_MODE; then
install -m 644 "$ANSWERFILE" /mnt/answerfile.json
fi
@ -310,8 +392,14 @@ fi
############################################
# CHROOT Configuration
############################################
# Capture the root partition UUID before entering the chroot; used in GRUB cmdline.
ROOT_UUID=$(blkid -s UUID -o value "$ROOT_PART")
# Variables are passed via `/usr/bin/env` on the arch-chroot command line rather
# than `export` in the outer shell — this makes the environment explicit and
# avoids leaking unrelated exported variables into the chroot.
# The heredoc delimiter is single-quoted ('CHROOT_EOF') to prevent the outer
# shell from expanding any $variables inside the heredoc body.
arch-chroot /mnt /usr/bin/env \
HOSTNAME="$HOSTNAME" \
USERNAME="$USERNAME" \
@ -326,16 +414,20 @@ arch-chroot /mnt /usr/bin/env \
set -euo pipefail
# Uncomment (create) the UTF-8 locale entry so locale-gen can compile it.
echo "en_US.UTF-8 UTF-8" > /etc/locale.gen
locale-gen
echo "LANG=en_US.UTF-8" > /etc/locale.conf
# vconsole.conf sets the console keymap permanently for all TTY sessions.
echo "KEYMAP=${KEYMAP}" > /etc/vconsole.conf
# Symlink the zoneinfo file; hwclock --systohc syncs the RTC to current system time.
ln -sf /usr/share/zoneinfo/Europe/Vienna /etc/localtime
hwclock --systohc
echo "$HOSTNAME" > /etc/hostname
# Enable NetworkManager at boot so the installed system has networking on first login.
systemctl enable NetworkManager
# Populate /etc/skel before user creation so useradd -m copies everything
@ -343,23 +435,23 @@ echo "Cloning dotfiles into /etc/skel..."
git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git /etc/skel/Dotfiles \
|| echo "Warning: dotfiles clone failed — clone manually after first boot."
# Seed /etc/skel with base shell dotfiles from the repo clone
if [[ -d /etc/skel/Dotfiles ]]; then
D=/etc/skel/Dotfiles
cp "$D/.zshrc" /etc/skel/.zshrc 2>/dev/null || true
cp "$D/.bashrc" /etc/skel/.bashrc 2>/dev/null || true
cp "$D/.vimrc" /etc/skel/.vimrc 2>/dev/null || true
fi
# Create standard XDG user directories in skel so they are present in every new home.
mkdir -p /etc/skel/{Desktop,Documents,Downloads,Music,Pictures,Public,Templates,Videos}
# -m: create home from /etc/skel; -G wheel: allow sudo; -s /bin/zsh: default shell.
useradd -m -G wheel -s /bin/zsh "$USERNAME"
# chpasswd reads "user:pass" from stdin to set the password non-interactively.
echo "$USERNAME:$USERPASS" | chpasswd
# Ensure all files under the new home dir are owned by the user (skel copy may be root-owned).
chown -R "$USERNAME:$USERNAME" "/home/$USERNAME"
# Grant wheel group full sudo access (ALL covers any host/user/group runas context).
echo "%wheel ALL=(ALL) ALL" >> /etc/sudoers
# Initramfs
# Initramfs hook selection:
# 1. FIDO2 root unlock: needs `systemd` + `sd-encrypt` for systemd-cryptsetup.
# 2. Password-only LUKS: classic `encrypt` hook (no systemd dependency in initramfs).
# 3. Unencrypted: minimal hook set — no encrypt hook needed.
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 btrfs filesystems keyboard keymap fsck)/' /etc/mkinitcpio.conf
elif [[ "$ENCRYPT_DISK" == "YES" ]]; then
@ -368,28 +460,38 @@ else
sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect microcode modconf kms consolefont block btrfs filesystems keyboard fsck)/' /etc/mkinitcpio.conf
fi
# -P regenerates all installed kernel presets (more thorough than -p <kernel>).
mkinitcpio -P
# GRUB
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
if [[ "$ENABLE_FIDO_ROOT" == "YES" ]]; then
# systemd-cryptsetup (sd-encrypt hook) reads rd.luks.name=<UUID>=<name>.
# rd.luks.options=fido2-device=auto instructs it to probe for a FIDO2 token.
GRUB_CMDLINE="rd.luks.name=$ROOT_UUID=cryptroot rd.luks.options=fido2-device=auto root=/dev/mapper/cryptroot"
else
# Classic encrypt hook syntax: cryptdevice=UUID=<UUID>:<dm-name>.
GRUB_CMDLINE="cryptdevice=UUID=$ROOT_UUID:cryptroot root=/dev/mapper/cryptroot"
fi
else
# Unencrypted btrfs: root= by UUID; rootflags selects the @ subvolume.
GRUB_CMDLINE="root=UUID=${ROOT_UUID} rootflags=subvol=@"
fi
# Inject the kernel command line; `|` delimiter avoids escaping `/` in device paths.
sed -i "s|^GRUB_CMDLINE_LINUX=.*|GRUB_CMDLINE_LINUX=\"$GRUB_CMDLINE\"|" /etc/default/grub
# Install GRUB to the EFI partition; --bootloader-id names the NVRAM/EFI menu entry.
grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB
# Generate grub.cfg from /etc/default/grub and discovered kernels/initramfs images.
grub-mkconfig -o /boot/grub/grub.cfg
# User login FIDO2 — directory + PAM only; key enrollment happens outside chroot
if [[ "$ENABLE_FIDO_USER" == "YES" ]]; then
# Create the Yubico config dir that pam_u2f expects for the u2f_keys file.
mkdir -p "/home/$USERNAME/.config/Yubico"
chown "$USERNAME:$USERNAME" "/home/$USERNAME/.config/Yubico"
# `cue` option: pam_u2f prints a prompt so the user knows to touch the key.
echo "auth required pam_u2f.so cue" >> /etc/pam.d/system-auth
fi
@ -401,10 +503,15 @@ if [[ "$ENABLE_FIDO_USER" == "YES" ]]; then
echo "Enrolling FIDO2 key for user login (outside chroot)..."
U2F_KEYFILE="/mnt/home/${USERNAME}/.config/Yubico/u2f_keys"
mkdir -p "/mnt/home/${USERNAME}/.config/Yubico"
# -o and -i set the origin/app-id to the hostname, scoping the credential so it
# cannot be replayed on a different system's PAM stack.
pamu2fcfg -u "$USERNAME" -o "pam://$HOSTNAME" -i "pam://$HOSTNAME" > "$U2F_KEYFILE"
# Query the UID/GID from inside the chroot to get the correct numeric IDs, since
# the live environment may have different /etc/passwd entries.
_NEWUID=$(arch-chroot /mnt id -u "$USERNAME" 2>/dev/null || echo "1000")
_NEWGID=$(arch-chroot /mnt id -g "$USERNAME" 2>/dev/null || echo "1000")
chown -R "$_NEWUID:$_NEWGID" "/mnt/home/${USERNAME}/.config/Yubico"
# 600: only the owning user can read or write the key file.
chmod 600 "$U2F_KEYFILE"
echo "FIDO2 key enrolled for $USERNAME."
fi
@ -413,30 +520,39 @@ fi
# DOTFILES SETUP (in-chroot, optional)
############################################
if $AF_MODE; then
# RUN_TUI was already populated from the answerfile earlier.
_DO_TUI="${RUN_TUI}"
else
read -rp "Run dotfiles TUI setup inside chroot now? [YES/no]: " _TUI_IN
# Default to YES when the user presses Enter without typing anything.
_TUI_IN="${_TUI_IN:-YES}"
[[ "${_TUI_IN^^}" == "YES" ]] && _DO_TUI="YES" || _DO_TUI="NO"
fi
if [[ "${_DO_TUI^^}" == "YES" ]]; then
# Grant temporary passwordless sudo so the TUI installer can call pacman/yay
# inside the chroot without a password. Removed immediately after the script exits.
echo "${USERNAME} ALL=(ALL) NOPASSWD: ALL" \
| arch-chroot /mnt tee /etc/sudoers.d/99-setup-nopasswd > /dev/null
echo "Running simple-install.sh as ${USERNAME} inside chroot..."
# `runuser -u` switches to the unprivileged user inside the chroot so that
# AUR helpers and dotfiles are owned/built by the correct UID.
arch-chroot /mnt runuser -u "${USERNAME}" -- \
bash "/home/${USERNAME}/Dotfiles/setup/simple-install.sh" \
|| echo "Warning: tui-install exited with errors — check ~/dotfiles-install.log in the new system."
# Remove the temporary no-password sudoers drop-in after setup completes.
arch-chroot /mnt rm -f /etc/sudoers.d/99-setup-nopasswd
fi
# Remove answerfile from new system after setup completes
# (it contains drive paths and config that are no longer needed post-install).
if $AF_MODE && [[ -f /mnt/answerfile.json ]]; then
rm -f /mnt/answerfile.json
fi
# Copy the install log into /boot so it is readable before the first login.
cp "$LOGFILE" /mnt/boot/ 2>/dev/null || true
echo "Installation complete! Log saved to /mnt/boot/$(basename "$LOGFILE")"

View File

@ -1,7 +1,27 @@
#!/usr/bin/env bash
# build.sh — build the M-Archy Arch Linux ISO + netboot artifacts
# =============================================================================
# build.sh — Build the M-Archy Arch Linux ISO + netboot artifacts
#
# Usage:
# PURPOSE:
# This is the primary build entry point for the M-Archy custom Arch Linux
# installer ISO. It takes the official archiso "releng" base profile and
# merges the M-Archy overlay on top of it, embedding custom installer
# scripts and an optional pre-filled answerfile for fully automated
# unattended installation. It also generates iPXE scripts for netboot-based
# deployment (PXE/WDS environments).
#
# WORKFLOW OVERVIEW:
# 1. Parse arguments (optional answerfile path, netboot URL, output dir).
# 2. Install archiso if missing.
# 3. Copy the upstream releng profile to a working directory.
# 4. Apply the M-Archy overlay (custom scripts, profiledef, mkinitcpio config).
# 5. Merge extra packages (packages.extra) into the releng package list.
# 6. Embed the installer shell scripts into the ISO's /root/installer/.
# 7. Optionally embed an answerfile.json for automated installs.
# 8. Run mkarchiso to produce the final .iso and netboot tarball.
# 9. Optionally write an iPXE chainload script for netboot.xyz / WDS.
#
# USAGE:
# bash build.sh [--preconf [FILE]] [--netboot-url URL] [OUT_DIR]
#
# --preconf Embed ~/answerfile.json into the ISO at /answerfile.json
@ -9,13 +29,24 @@
# --netboot-url URL Base URL where netboot artifacts will be served
# (generates m-archy-netboot.ipxe in OUT_DIR)
# OUT_DIR Output directory (default: ~/m-archy-out, or $OUT_DIR env)
# =============================================================================
set -euo pipefail
# set -e → abort on any command that exits non-zero
# set -u → abort if an unset variable is referenced (catches typos)
# set -o pipefail → a pipe fails if any command in it fails (not just the last)
# Resolve this script's directory and the dotfiles root, regardless of where
# the caller's working directory is. Using BASH_SOURCE[0] rather than $0 is
# safer when the script is sourced or called with a relative path.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DOTFILES_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
# ── Argument parsing ───────────────────────────────────────────────────────────
# We support both --flag value and --flag=value syntax.
# PRECONF_FILE → path to JSON answerfile to embed (empty = no answerfile)
# NETBOOT_URL → HTTP base URL for generated iPXE script (empty = skip)
# OUT_ARG → positional output directory override
PRECONF_FILE=""
NETBOOT_URL=""
OUT_ARG=""
@ -23,7 +54,8 @@ OUT_ARG=""
while [[ $# -gt 0 ]]; do
case "$1" in
--preconf)
# Optional next arg: a file path (doesn't start with -)
# If the next argument looks like a path (doesn't start with -),
# treat it as the answerfile path; otherwise use the default location.
if [[ $# -gt 1 && "${2:0:1}" != "-" ]]; then
PRECONF_FILE="$2"; shift
else
@ -32,6 +64,7 @@ while [[ $# -gt 0 ]]; do
shift
;;
--preconf=*)
# Handle --preconf=/path/to/file syntax (strip prefix up to =)
PRECONF_FILE="${1#--preconf=}"
shift
;;
@ -47,57 +80,104 @@ while [[ $# -gt 0 ]]; do
echo "Unknown flag: $1" >&2; exit 1
;;
*)
# Any non-flag argument is treated as the output directory
OUT_ARG="$1"; shift
;;
esac
done
# WORK_DIR: scratch space where mkarchiso does its work (can be huge — squashfs
# compression of the full airootfs happens here).
WORK_DIR="${WORK_DIR:-$HOME/m-archy-build}"
# OUT_DIR: where the final .iso and netboot tarball are written.
# Priority: positional arg > $OUT_DIR env var > default ~/m-archy-out
OUT_DIR="${OUT_ARG:-${OUT_DIR:-$HOME/m-archy-out}}"
# PROFILE: the assembled profile directory passed to mkarchiso.
# We copy releng here and then apply our overlay on top.
PROFILE="$WORK_DIR/profile"
# RELENG: the official Arch Linux "release engineering" base profile, installed
# with the archiso package. It provides sane defaults for pacman.conf, EFI
# boot entries, and a working live environment.
RELENG="/usr/share/archiso/configs/releng"
# ── Ensure archiso is installed ───────────────────────────────────────────────
# mkarchiso is the tool that actually assembles the ISO from a profile directory.
# It is only available after installing the archiso package.
if ! command -v mkarchiso &>/dev/null; then
echo "Installing archiso..."
sudo pacman -S --noconfirm archiso
fi
# Sanity check: the releng base profile must exist.
# If archiso installed correctly, this path will always be present.
[[ -d "$RELENG" ]] || { echo "ERROR: $RELENG not found — is archiso installed?"; exit 1; }
# Validate answerfile early if --preconf was given
# ── Validate answerfile early (fail fast before the long build) ───────────────
# If --preconf was given, verify the file exists and is valid JSON now rather
# than discovering the problem after a 10-minute build.
if [[ -n "$PRECONF_FILE" ]]; then
[[ -f "$PRECONF_FILE" ]] \
|| { echo "ERROR: answerfile not found: $PRECONF_FILE"; exit 1; }
# jq empty parses JSON and exits 0 if valid, non-zero if malformed.
# Fall through gracefully if jq is not installed.
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
# ── Clean and create working directories ─────────────────────────────────────
# Remove any previous build artifacts to ensure a clean, reproducible build.
# mkarchiso can behave unexpectedly if the profile or work directory has stale state.
rm -rf "$WORK_DIR"
mkdir -p "$WORK_DIR" "$OUT_DIR"
# ── Assemble the profile from releng + M-Archy overlay ───────────────────────
echo "Copying releng base profile..."
# Start with a full copy of upstream releng so we inherit all its boot
# entries, pacman.conf, syslinux/systemd-boot configs, etc.
cp -r "$RELENG" "$PROFILE"
echo "Applying M-Archy overlay..."
# Merge our custom airootfs overlay ON TOP of the releng copy.
# Files in our overlay replace or extend the releng defaults.
# The trailing /. ensures we copy the directory contents, not the directory itself.
cp -r "$SCRIPT_DIR/overlay/airootfs/." "$PROFILE/airootfs/"
echo "Replacing profiledef..."
# profiledef.sh controls ISO metadata (name, label, build modes, boot modes,
# compression settings, and file permissions in the final image).
# We replace the releng default entirely with our customized version.
cp "$SCRIPT_DIR/overlay/profiledef.sh" "$PROFILE/profiledef.sh"
echo "Adding extra packages..."
# packages.extra lists additional packages beyond releng's defaults.
# We merge them into packages.x86_64 (the file mkarchiso reads for pacman).
# Strategy: read line-by-line, skip blank lines and comments (#),
# and append each package only if it is not already in the list (idempotent).
while IFS= read -r pkg || [[ -n "$pkg" ]]; do
[[ -z "$pkg" || "$pkg" == \#* ]] && continue
grep -qxF "$pkg" "$PROFILE/packages.x86_64" || echo "$pkg" >> "$PROFILE/packages.x86_64"
done < "$SCRIPT_DIR/overlay/packages.extra"
# ── Embed installer scripts ────────────────────────────────────────────────────
# These three scripts live in the main setup/ directory of the dotfiles repo
# and implement the actual Arch Linux installation logic. They are placed in
# /root/installer/ on the live ISO so the auto-launch scripts can find them.
echo "Embedding installer scripts..."
mkdir -p "$PROFILE/airootfs/root/installer"
# Guided interactive installer — walks the user through partitioning, locale, etc.
cp "$DOTFILES_DIR/setup/archbaseos-guided-install.sh" "$PROFILE/airootfs/root/installer/"
# Automated unattended installer — reads /answerfile.json and installs silently.
cp "$DOTFILES_DIR/setup/arch-autoinstall.sh" "$PROFILE/airootfs/root/installer/"
# Reset script — wipes and reinstalls the system while preserving /home.
cp "$DOTFILES_DIR/setup/reset-arch.sh" "$PROFILE/airootfs/root/installer/"
# Make all scripts executable. The archiso tool preserves these bits in the
# squashfs, so they will be executable on the live system too.
chmod 755 \
"$PROFILE/airootfs/root/launch.sh" \
"$PROFILE/airootfs/root/.automated_script.sh" \
@ -105,20 +185,36 @@ chmod 755 \
"$PROFILE/airootfs/root/installer/"*.sh
# ── Embed answerfile (--preconf) ───────────────────────────────────────────────
# An answerfile baked into the ISO lets machines boot and install completely
# hands-free — useful for bulk deployment via PXE. Without it, the guided
# installer starts automatically instead.
if [[ -n "$PRECONF_FILE" ]]; then
echo "Embedding answerfile: $PRECONF_FILE → /answerfile.json"
# install -m 644 creates the destination with the given permissions,
# equivalent to cp + chmod but in one atomic step.
install -m 644 "$PRECONF_FILE" "$PROFILE/airootfs/answerfile.json"
fi
echo "Building ISO (this may take a while)..."
# mkarchiso needs root to mount loopback devices, write to /proc-style paths,
# and set file ownership inside the squashfs correctly.
# -v: verbose (shows progress)
# -w: work directory (large temporary build area)
# -o: output directory for the final .iso and netboot tarball
sudo mkarchiso -v -w "$WORK_DIR/mkarchiso" -o "$OUT_DIR" "$PROFILE"
# After the build, mkarchiso leaves files owned by root. Fix ownership so the
# calling user can manipulate the output without sudo.
sudo chmod -R 777 "$WORK_DIR" "$OUT_DIR"
sudo chown -R "$(id -u):$(id -g)" "$WORK_DIR" "$OUT_DIR"
echo
echo "Done."
# List the produced ISO file(s). The || true prevents the script from failing
# when no .iso files exist (e.g., if only netboot mode was built).
ls -lh "$OUT_DIR/"*.iso 2>/dev/null || true
# Inform the user whether an automated or guided install will start on boot.
if [[ -n "$PRECONF_FILE" ]]; then
echo "Answerfile embedded — automated install will activate on boot."
else
@ -126,18 +222,26 @@ else
fi
# ── Netboot artifacts ──────────────────────────────────────────────────────────
# mkarchiso's 'netboot' build mode (set in profiledef.sh) produces a tarball
# containing the kernel, initramfs, and airootfs.sfs ready for HTTP-based PXE
# boot. We detect it here and optionally generate an iPXE chainload script.
NETBOOT_TARBALL="$(ls "$OUT_DIR/"*-netboot-*.tar.gz 2>/dev/null | head -n1 || true)"
if [[ -n "$NETBOOT_TARBALL" ]]; then
echo
echo "Netboot artifact: $NETBOOT_TARBALL"
echo " Extract and serve its contents from an HTTP server, then boot via PXE."
echo " Internal layout (relative to tarball root):"
# Show the tarball contents indented for readability.
tar -tzf "$NETBOOT_TARBALL" | sed 's/^/ /'
if [[ -n "$NETBOOT_URL" ]]; then
# Strip trailing slash for consistency
# Strip trailing slash so our URL concatenations are consistent.
BASE_URL="${NETBOOT_URL%/}"
IPXE_FILE="$OUT_DIR/m-archy-netboot.ipxe"
# Generate an iPXE script that can be chainloaded from netboot.xyz or
# served directly from a WDS/TFTP server. The script tells iPXE where
# to find the kernel, initramfs, and squashfs root over HTTP.
cat > "$IPXE_FILE" <<IPXE
#!ipxe
# M-Archy Arch Linux Installer — PXE Boot

View File

@ -1,10 +1,48 @@
#!/usr/bin/env bash
# =============================================================================
# .automated_script.sh — Entry point triggered by the archiso live environment
#
# PURPOSE:
# This script is called by the archiso systemd getty service (or directly
# from .zlogin) immediately after the root user logs in on the live ISO.
# It acts as the single decision point that determines whether to run the
# automated unattended installer or the guided interactive installer.
#
# HOW IT WORKS:
# archiso's default releng profile ships a .automated_script.sh that starts
# a graphical session or similar. We replace it with this simpler dispatcher.
#
# The presence or absence of /answerfile.json is the determining signal:
# - EXISTS → machine has been configured for unattended deployment
# (answerfile was embedded by build.sh --preconf, or placed
# on the live system via another mechanism such as a USB drive).
# → launch.sh is called in "auto" mode and /answerfile.json is passed as
# the configuration source for arch-autoinstall.sh.
#
# - MISSING → this is a standard interactive boot (no pre-configuration).
# → launch.sh is called in "guided" mode which starts the interactive
# guided installer after prompting for keyboard layout and action.
#
# WHY exec:
# Using exec replaces this process with launch.sh rather than spawning a
# child. This means the PID stays the same and signals propagate correctly.
# If launch.sh exits, the shell exits too, which is the correct behaviour
# for an installer that terminates after completing or being cancelled.
# =============================================================================
set -euo pipefail
ANSWERFILE="/answerfile.json"
# Check whether an answerfile was embedded in the ISO (or placed on the live
# system). The answerfile contains all installation parameters as JSON, allowing
# completely unattended installation with zero user interaction.
if [[ -f "$ANSWERFILE" ]]; then
# Automated mode: pass "auto" as the first arg and the answerfile path as
# the second. launch.sh will forward these to arch-autoinstall.sh.
exec /root/launch.sh auto "$ANSWERFILE"
else
# Guided mode: start the interactive installer. The user will be prompted
# for keyboard layout and installation action before anything happens.
exec /root/launch.sh guided
fi

View File

@ -1,7 +1,26 @@
#!/usr/bin/env bash
# wds-deploy.sh — Package M-Archy archiso netboot artifacts for WDS + PXELinux deployment
# =============================================================================
# wds-deploy.sh — Package M-Archy archiso netboot artifacts for WDS + PXELinux
#
# Usage:
# PURPOSE:
# Windows Deployment Services (WDS) on a Windows Server can serve PXE boot
# images to clients via TFTP. This script:
# 1. Builds the M-Archy ISO + netboot tarball (by calling build.sh), or
# reuses an existing tarball if --no-rebuild is given.
# 2. Extracts the kernel and initramfs from the netboot tarball.
# 3. Builds a TFTP directory tree compatible with WDS + PXELinux (syslinux).
# 4. Builds an HTTP directory tree for the airootfs.sfs squashfs root
# (Arch's initrd fetches this over HTTP at boot time — TFTP is too slow).
# 5. Generates a pxelinux.cfg/default boot menu with multiple options.
# 6. Optionally zips the TFTP tree for easy transfer to a Windows Server.
#
# WHY WDS + PXELinux:
# WDS natively speaks the Microsoft NBP (network boot program) protocol.
# By configuring WDS to serve pxelinux.0 as the boot file, it becomes a
# standards-compliant TFTP server that can chainload syslinux PXELinux,
# which then presents our custom menu and loads the Arch kernel + initramfs.
#
# USAGE:
# bash wds-deploy.sh --http-srv URL [OPTIONS] [OUT_DIR]
#
# --http-srv URL HTTP base URL where arch netboot files will be served
@ -14,29 +33,34 @@
# --no-rebuild Skip the archiso build if a netboot tarball already exists
# OUT_DIR Output directory (default: ~/m-archy-out)
#
# Output layout (inside OUT_DIR/wds-deploy/):
# OUTPUT LAYOUT (inside OUT_DIR/wds-deploy/):
# TFTP/ Copy contents to WDS TFTP root: C:\RemoteInstall\Boot\x64\
# HTTP/ Serve as HTTP root at the URL given to --http-srv
# wds-tftp.zip Zip of TFTP/ — drop onto the Windows Server directly
#
# WDS deployment steps:
# WDS DEPLOYMENT STEPS:
# 1. Serve HTTP/ over IIS/Nginx at the --http-srv URL
# 2. Extract wds-tftp.zip into C:\RemoteInstall\Boot\x64\
# 3. In WDS console → server Properties → Boot tab:
# Set "Default boot program" for x64 to: Boot\x64\pxelinux.0
# 4. PXE-boot a client — the M-Archy menu appears
# =============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# syslinux provides pxelinux.0 and the companion .c32 module files.
# On Arch Linux these live in /usr/lib/syslinux/bios/ after installing the
# syslinux package. WDS serves these to PXE-booting clients via TFTP.
SYSLINUX_BIOS="/usr/lib/syslinux/bios"
# ── Argument parsing ───────────────────────────────────────────────────────────
HTTP_SRV=""
TFTP_PREFIX="m-archy"
PRECONF_ARGS=()
NO_REBUILD=0
OUT_ARG=""
HTTP_SRV="" # Required: base HTTP URL where airootfs.sfs is served
TFTP_PREFIX="m-archy" # Subdirectory under TFTP root for kernel + initramfs
PRECONF_ARGS=() # Forwarded to build.sh if --preconf was given
NO_REBUILD=0 # 1 = skip build and reuse existing netboot tarball
OUT_ARG="" # Positional output directory override
while [[ $# -gt 0 ]]; do
case "$1" in
@ -51,12 +75,14 @@ while [[ $# -gt 0 ]]; do
--tftp-prefix=*)
TFTP_PREFIX="${1#--tftp-prefix=}"; shift ;;
--preconf)
# Forward --preconf [optional-file] to build.sh verbatim.
if [[ $# -gt 1 && "${2:0:1}" != "-" ]]; then
PRECONF_ARGS=(--preconf "$2"); shift 2
else
PRECONF_ARGS=(--preconf); shift
fi ;;
--preconf=*)
# Forward --preconf=file to build.sh verbatim.
PRECONF_ARGS=("$1"); shift ;;
--no-rebuild)
NO_REBUILD=1; shift ;;
@ -68,9 +94,13 @@ while [[ $# -gt 0 ]]; do
done
OUT_DIR="${OUT_ARG:-${OUT_DIR:-$HOME/m-archy-out}}"
# WDS_DIR is a subdirectory of OUT_DIR where we build the TFTP + HTTP trees.
WDS_DIR="$OUT_DIR/wds-deploy"
# ── Validate ──────────────────────────────────────────────────────────────────
# ── Validate required arguments ───────────────────────────────────────────────
# --http-srv is mandatory because the Arch initrd must know where to download
# airootfs.sfs from at boot time. Without it, the kernel boots but the root
# filesystem cannot be mounted and the system hangs.
if [[ -z "$HTTP_SRV" ]]; then
echo "ERROR: --http-srv <URL> is required." >&2
echo " The Arch initrd fetches airootfs.sfs over HTTP at boot." >&2
@ -78,26 +108,36 @@ if [[ -z "$HTTP_SRV" ]]; then
exit 1
fi
HTTP_SRV="${HTTP_SRV%/}" # strip trailing slash
# Strip trailing slash so URL concatenations are consistent throughout.
HTTP_SRV="${HTTP_SRV%/}"
# ── Ensure syslinux is available ──────────────────────────────────────────────
# pxelinux.0 is the PXE network boot program (NBP) that WDS sends to clients.
# Without syslinux installed we cannot produce the TFTP tree.
if [[ ! -f "$SYSLINUX_BIOS/pxelinux.0" ]]; then
echo "syslinux not found — installing..."
sudo pacman -S --noconfirm syslinux
fi
# Second check after attempted install — exit loudly if still missing.
if [[ ! -f "$SYSLINUX_BIOS/pxelinux.0" ]]; then
echo "ERROR: $SYSLINUX_BIOS/pxelinux.0 still not found after install." >&2
exit 1
fi
# ── Build ISO + netboot tarball (unless --no-rebuild) ─────────────────────────
# mkarchiso's 'netboot' build mode produces a .tar.gz alongside the .iso.
# The tarball contains the kernel, initramfs, and the squashfs tree needed
# for PXE netbooting. We extract kernel+initramfs to TFTP; the squashfs tree
# goes to the HTTP root.
NETBOOT_TARBALL="$(ls "$OUT_DIR/"*-netboot-*.tar.gz 2>/dev/null | head -n1 || true)"
if [[ "$NO_REBUILD" -eq 1 && -n "$NETBOOT_TARBALL" ]]; then
echo "Skipping build — using existing tarball: $(basename "$NETBOOT_TARBALL")"
else
echo "Building archiso (this may take a while)..."
# Pass PRECONF_ARGS only when it is non-empty; the ${array[@]+"${array[@]}"}
# idiom avoids "unbound variable" errors when the array is empty (set -u).
bash "$SCRIPT_DIR/build.sh" "${PRECONF_ARGS[@]+"${PRECONF_ARGS[@]}"}" "$OUT_DIR"
NETBOOT_TARBALL="$(ls "$OUT_DIR/"*-netboot-*.tar.gz 2>/dev/null | head -n1 || true)"
fi
@ -108,12 +148,17 @@ fi
echo "Using netboot tarball: $(basename "$NETBOOT_TARBALL")"
# ── Extract netboot tarball ───────────────────────────────────────────────────
# We extract into a temporary directory, then pick out the specific files we
# need for each destination (TFTP vs HTTP) rather than serving the tarball
# layout directly.
EXTRACT_DIR="$OUT_DIR/.netboot-extracted"
rm -rf "$EXTRACT_DIR"
mkdir -p "$EXTRACT_DIR"
echo "Extracting netboot tarball..."
tar -xzf "$NETBOOT_TARBALL" -C "$EXTRACT_DIR"
# Locate the three key items inside the extracted tree.
# Using find + head -n1 handles variations in tarball layout across archiso versions.
VMLINUZ="$(find "$EXTRACT_DIR" -name 'vmlinuz-linux' | head -n1 || true)"
INITRAMFS="$(find "$EXTRACT_DIR" -name 'initramfs-linux.img' | head -n1 || true)"
ARCH_DIR="$(find "$EXTRACT_DIR" -maxdepth 2 -type d -name 'arch' | head -n1 || true)"
@ -127,15 +172,22 @@ ARCH_DIR="$(find "$EXTRACT_DIR" -maxdepth 2 -type d -name 'arch' | head -n1 || t
# TFTP root layout (mirrors what WDS serves via TFTP):
# pxelinux.0 PXELinux bootloader
# ldlinux.c32 required by pxelinux.0
# menu.c32 + libcom32.c32 + libutil.c32 text boot menu
# pxelinux.cfg/default boot menu config
# <tftp-prefix>/arch/boot/x86_64/vmlinuz-linux
# <tftp-prefix>/arch/boot/x86_64/initramfs-linux.img
# menu.c32 + libcom32.c32 + libutil.c32 text boot menu modules
# pxelinux.cfg/default boot menu configuration
# <tftp-prefix>/arch/boot/x86_64/vmlinuz-linux Linux kernel
# <tftp-prefix>/arch/boot/x86_64/initramfs-linux.img initrd (with archiso hooks)
#
# HTTP root layout (served at $HTTP_SRV/ over IIS/Nginx/Apache):
# arch/x86_64/airootfs.sfs fetched by initrd at boot
# arch/x86_64/airootfs.sfs.sha512
# arch/pkglist.x86_64.txt (and any other netboot files)
# arch/x86_64/airootfs.sfs the squashfs root filesystem fetched at boot
# arch/x86_64/airootfs.sfs.sha512 integrity checksum
# arch/pkglist.x86_64.txt (and any other netboot metadata files)
#
# WHY two servers (TFTP + HTTP)?
# TFTP is required by the PXE protocol for the initial NBP handshake and
# kernel/initramfs delivery. However, TFTP is extremely slow for large files.
# The airootfs.sfs (~500 MB squashfs) is downloaded by the initrd over HTTP,
# which is orders of magnitude faster. This two-tier approach is the standard
# Arch Linux netboot pattern.
TFTP_ROOT="$WDS_DIR/TFTP"
HTTP_ROOT="$WDS_DIR/HTTP"
@ -147,6 +199,11 @@ mkdir -p \
"$HTTP_ROOT"
# ── Copy syslinux modules ─────────────────────────────────────────────────────
# pxelinux.0 — the actual NBP that WDS sends to the client via DHCP option 67.
# ldlinux.c32 — core syslinux library, required by pxelinux.0.
# menu.c32 — text-based interactive boot menu module.
# libcom32.c32 — common syslinux library needed by menu.c32.
# libutil.c32 — utility library needed by menu.c32.
echo "Copying syslinux PXELinux modules..."
REQUIRED_MODS=(pxelinux.0 ldlinux.c32 menu.c32 libcom32.c32 libutil.c32)
MISSING_MODS=()
@ -155,6 +212,8 @@ for mod in "${REQUIRED_MODS[@]}"; do
if [[ -f "$SYSLINUX_BIOS/$mod" ]]; then
cp "$SYSLINUX_BIOS/$mod" "$TFTP_ROOT/"
else
# Older syslinux versions may not include all modules — warn but continue.
# pxelinux.0 and ldlinux.c32 are essential; menu modules are optional.
MISSING_MODS+=("$mod")
fi
done
@ -164,23 +223,32 @@ if [[ ${#MISSING_MODS[@]} -gt 0 ]]; then
fi
# ── Copy kernel and initramfs ─────────────────────────────────────────────────
# These go into the TFTP tree because PXELinux loads them from the TFTP server
# as specified in pxelinux.cfg/default (KERNEL + initrd= directives).
echo "Copying kernel and initramfs..."
cp "$VMLINUZ" "$TFTP_ROOT/$TFTP_PREFIX/arch/boot/x86_64/vmlinuz-linux"
cp "$INITRAMFS" "$TFTP_ROOT/$TFTP_PREFIX/arch/boot/x86_64/initramfs-linux.img"
# ── Copy HTTP content (airootfs squashfs and supporting files) ────────────────
# The entire arch/ directory from the netboot tarball goes to the HTTP root.
# The Arch initrd uses the archiso_http_srv and archisobasedir kernel parameters
# to locate and download airootfs.sfs from this path.
echo "Copying HTTP content (airootfs + supporting files)..."
cp -r "$ARCH_DIR" "$HTTP_ROOT/arch"
# ── Generate pxelinux.cfg/default ────────────────────────────────────────────
# This file is what PXELinux reads to build the boot menu. It must be at
# pxelinux.cfg/default relative to the TFTP root.
echo "Writing pxelinux.cfg/default..."
# Kernel path is relative to the TFTP root
# Kernel path is relative to the TFTP root (PXELinux convention).
KERNEL_PATH="${TFTP_PREFIX}/arch/boot/x86_64/vmlinuz-linux"
INITRD_PATH="${TFTP_PREFIX}/arch/boot/x86_64/initramfs-linux.img"
# archiso_http_srv must end with a slash; archisobasedir is the subdir within it
# that contains the x86_64/ squashfs tree
# archiso kernel parameters:
# archiso_http_srv= — base URL of the HTTP server (must end with /)
# archisobasedir= — subdirectory within the HTTP server containing arch/x86_64/
# ip=dhcp — tell the initrd to acquire an IP via DHCP (required for HTTP)
APPEND_BASE="initrd=${INITRD_PATH} archiso_http_srv=${HTTP_SRV}/ archisobasedir=arch ip=dhcp"
cat > "$TFTP_ROOT/pxelinux.cfg/default" <<PXECFG
@ -212,9 +280,12 @@ LABEL local
PXECFG
# ── Zip TFTP directory for easy transfer to Windows Server ────────────────────
# Windows admins often prefer to receive a single zip rather than a directory
# tree via SCP. The zip can be extracted directly in C:\RemoteInstall\Boot\x64\.
ZIP_FILE="$WDS_DIR/wds-tftp.zip"
echo "Creating wds-tftp.zip..."
if command -v zip &>/dev/null; then
# cd into TFTP_ROOT so the zip paths are relative (no leading path prefix).
(cd "$TFTP_ROOT" && zip -r "$ZIP_FILE" .)
echo "Created: $ZIP_FILE"
else

View File

@ -1,30 +1,71 @@
#!/bin/bash
# ╔══════════════════════════════════════════════════════════════════════════════╗
# ║ setup/audit-packages.sh — Package source audit tool ║
# ║ ║
# ║ PURPOSE: ║
# ║ Verifies that installed packages come from the expected source ║
# ║ (official pacman repos, AUR, or Flatpak). Reports: ║
# ║ - Missing packages that should be installed ║
# ║ - Packages installed from the wrong source (e.g. pacman pkg from AUR) ║
# ║ - Unexpected foreign/AUR packages not declared in setup scripts ║
# ║ ║
# ║ USAGE: ║
# ║ bash audit-packages.sh # report only ║
# ║ bash audit-packages.sh --fix # report + auto-reinstall wrong-source ║
# ║ bash audit-packages.sh -f # shorthand for --fix ║
# ║ ║
# ║ WHY THIS TOOL EXISTS: ║
# ║ On Arch, some packages exist in both the official repos AND the AUR ║
# ║ (often with a different build/version). If an AUR package shadows an ║
# ║ official one, pacman -Syu won't update it. This script detects drift. ║
# ║ ║
# ║ EXIT CODES: ║
# ║ 0 — all checks passed ║
# ║ 1 — one or more issues found ║
# ╚══════════════════════════════════════════════════════════════════════════════╝
# Verifies installed packages come from the expected source (pacman/AUR/flatpak).
# Reports missing packages, wrong-source installs, and unexpected foreign packages.
# Pass --fix / -f to automatically reinstall packages from the correct source.
set -euo pipefail
# ── Output formatting ──────────────────────────────────────────────────────────
# ANSI color codes for colored terminal output
RED="\e[31m"; YELLOW="\e[33m"; GREEN="\e[32m"; CYAN="\e[36m"; BOLD="\e[1m"; RESET="\e[0m"
ok() { echo -e " ${GREEN}${RESET} $1"; }
warn() { echo -e " ${YELLOW}${RESET} $1"; }
err() { echo -e " ${RED}${RESET} $1"; }
hdr() { echo -e "\n${BOLD}${CYAN}$1${RESET}"; }
fix() { echo -e " ${CYAN}${RESET} $1"; }
ok() { echo -e " ${GREEN}${RESET} $1"; } # Green checkmark: package is correct
warn() { echo -e " ${YELLOW}${RESET} $1"; } # Yellow warning: non-critical issue
err() { echo -e " ${RED}${RESET} $1"; } # Red X: package missing or wrong
hdr() { echo -e "\n${BOLD}${CYAN}$1${RESET}"; } # Bold cyan section header
fix() { echo -e " ${CYAN}${RESET} $1"; } # Cyan spinner: fix action in progress
# ── Fix mode flag ─────────────────────────────────────────────────────────────
# Parse the --fix / -f argument. When FIX=1, wrong-source packages will be
# automatically reinstalled from the correct source at the end of the audit.
FIX=0
[[ "${1:-}" == "--fix" || "${1:-}" == "-f" ]] && FIX=1
# ── Issue counter ─────────────────────────────────────────────────────────────
# ISSUES tracks the number of problems found. The final exit code is 1 if > 0.
ISSUES=0
flag() { ISSUES=$((ISSUES + 1)); }
flag() { ISSUES=$((ISSUES + 1)); } # Increment the issue counter
# ── Wrong-source package lists ────────────────────────────────────────────────
# These arrays collect packages that need reinstallation when --fix is used.
WRONG_SOURCE_OFFICIAL=() # expected from pacman, installed from AUR
WRONG_SOURCE_AUR=() # expected from AUR, installed from official
# ---------------------------------------------------------------------------
# Expected package sources — keep in sync with setup/modules/
# ---------------------------------------------------------------------------
# IMPORTANT: When you add packages to any module script, add them here too.
# The lists are manually maintained to stay in sync with what modules install.
# ── PACMAN_PKGS: packages that MUST come from official Arch repositories ──────
# If any of these are found to be installed from the AUR instead, that's a drift
# issue — the AUR version may not receive official security updates.
PACMAN_PKGS=(
# core-packages.sh
7zip arch-install-scripts atftp atool
@ -64,33 +105,43 @@ PACMAN_PKGS=(
kew
)
# ── AUR_PKGS: packages that MUST come from the AUR ───────────────────────────
# If any of these are found in the official repos instead, it may mean Arch has
# promoted them — but could also mean a naming collision. Flagged for review.
# NOTE: AUR packages are treated as optional (warn, not err) when missing,
# because they may be legitimately excluded on some systems.
AUR_PKGS=(
# hyprland.sh AUR portion
# hyprland.sh AUR portion (not in official repos)
hyprland-workspaces vicinae-bin bluetuith wvkbd iwmenu
walker-bin ulauncher bzmenu wofi-calc bri chamel
# optional apps
pamtester
pinta
localsend
vesktop
onlyoffice-bin
vintagestory
wprs-git
zfs-dkms
# optional apps (only installed if selected in TUI)
pamtester # PAM authentication tester
pinta # Simple image editor (paint.net-like)
localsend # LAN file transfer
vesktop # Discord with Vencord themes
onlyoffice-bin # Office suite (pre-built binary from AUR)
vintagestory # Survival game
wprs-git # Wayland proxy (git version)
zfs-dkms # ZFS kernel module
)
# ── FLATPAK_PKGS: apps installed via Flatpak (Flathub) ───────────────────────
# These are cross-distro app bundles. Listed by their Flatpak app ID.
FLATPAK_PKGS=(
org.prismlauncher.PrismLauncher
org.prismlauncher.PrismLauncher # Minecraft launcher
)
# ---------------------------------------------------------------------------
# Snapshot current state
# ---------------------------------------------------------------------------
# Take a snapshot of what's installed now so we don't repeatedly call pacman.
# This also ensures consistent results across the whole run.
ALL_INSTALLED=$(pacman -Qq 2>/dev/null)
FOREIGN=$(pacman -Qmq 2>/dev/null) # AUR / manually installed
OFFICIAL=$(pacman -Qnq 2>/dev/null) # in a sync repo
ALL_INSTALLED=$(pacman -Qq 2>/dev/null) # All installed packages (name only)
FOREIGN=$(pacman -Qmq 2>/dev/null) # AUR / manually installed (foreign) packages
OFFICIAL=$(pacman -Qnq 2>/dev/null) # Packages from official sync repos
# Helper functions for membership testing — grep -qx matches whole lines exactly
is_installed() { echo "$ALL_INSTALLED" | grep -qx "$1"; }
is_from_official() { echo "$OFFICIAL" | grep -qx "$1"; }
is_from_aur() { echo "$FOREIGN" | grep -qx "$1"; }
@ -98,6 +149,10 @@ is_from_aur() { echo "$FOREIGN" | grep -qx "$1"; }
# ---------------------------------------------------------------------------
# 1. Check pacman packages
# ---------------------------------------------------------------------------
# For each expected official package:
# - NOT installed: hard error (✘) — increment issue counter
# - Installed from AUR: warning (⚠) — drift; increment counter, queue for fix
# - Installed from official: pass (✔)
hdr "1/3 Official-repo packages (pacman)"
@ -105,6 +160,8 @@ for pkg in "${PACMAN_PKGS[@]}"; do
if ! is_installed "$pkg"; then
err "$pkg — NOT INSTALLED"; flag
elif is_from_aur "$pkg"; then
# This package should come from official repos but is installed from AUR.
# This means `pacman -Syu` won't update it — potential security gap.
warn "$pkg — installed, but from AUR instead of official repo"; flag
WRONG_SOURCE_OFFICIAL+=("$pkg")
else
@ -115,13 +172,21 @@ done
# ---------------------------------------------------------------------------
# 2. Check AUR packages
# ---------------------------------------------------------------------------
# For each expected AUR package:
# - NOT installed: soft warning — AUR packages are typically optional
# - Installed from official repo: warning — may get wrong version from Arch
# - Installed from AUR: pass (✔)
hdr "2/3 AUR packages (yay)"
for pkg in "${AUR_PKGS[@]}"; do
if ! is_installed "$pkg"; then
# AUR packages are often optional (selected in TUI installer), so this
# is a warning rather than an error — intentional omission is common.
warn "$pkg — not installed (optional — may be intentional)"
elif is_from_official "$pkg"; then
# This package should come from AUR but is installed from official repos.
# The official version may differ (newer or older) — flagged for review.
warn "$pkg — installed from official repo, not AUR (may need AUR build for correct version)"; flag
WRONG_SOURCE_AUR+=("$pkg")
else
@ -132,10 +197,13 @@ done
# ---------------------------------------------------------------------------
# 3. Check flatpak packages
# ---------------------------------------------------------------------------
# Flatpak packages are checked separately using the `flatpak` command.
# Missing Flatpaks are soft warnings since they're always optional.
hdr "3/3 Flatpak packages"
if command -v flatpak &>/dev/null; then
# List installed Flatpak apps (not runtimes) in the 'application' column only
FLATPAK_INSTALLED=$(flatpak list --app --columns=application 2>/dev/null)
for pkg in "${FLATPAK_PKGS[@]}"; do
if echo "$FLATPAK_INSTALLED" | grep -qx "$pkg"; then
@ -151,11 +219,16 @@ fi
# ---------------------------------------------------------------------------
# 4. Flag unexpected foreign (AUR) packages not in our lists
# ---------------------------------------------------------------------------
# Any AUR package installed on the system but NOT in our AUR_PKGS list is
# unexpected. These may have been installed manually or by other tools.
# They're highlighted for human review — not counted as issues.
hdr "Bonus: foreign packages not declared in setup scripts"
# Build a newline-separated set of declared AUR packages for grep matching
AUR_SET=$(printf "%s\n" "${AUR_PKGS[@]}")
while IFS= read -r pkg; do
# If the foreign package is not in our declared AUR list, highlight it
if ! echo "$AUR_SET" | grep -qx "$pkg"; then
echo -e " ${CYAN}?${RESET} $pkg — foreign package not in setup scripts"
fi
@ -164,6 +237,8 @@ done <<< "$FOREIGN"
# ---------------------------------------------------------------------------
# Fix: reinstall wrong-source packages
# ---------------------------------------------------------------------------
# When --fix is passed, reinstall any packages found in the wrong source.
# Official packages are reinstalled with pacman; AUR packages with yay --aur.
FIXED=0
@ -172,19 +247,23 @@ if [ "$FIX" -eq 1 ]; then
hdr "Reinstalling wrong-source packages"
fi
# Reinstall from official repo (packages that were incorrectly from AUR)
for pkg in "${WRONG_SOURCE_OFFICIAL[@]}"; do
fix "Reinstalling $pkg from official repo (was AUR)..."
# pacman -S without --aur ensures it comes from the sync database
if sudo pacman -S --noconfirm "$pkg"; then
ok "$pkg reinstalled from official repo"
FIXED=$((FIXED + 1))
ISSUES=$((ISSUES - 1))
ISSUES=$((ISSUES - 1)) # Remove from issue count since it's now fixed
else
err "$pkg reinstall failed"
fi
done
# Reinstall from AUR (packages that were incorrectly from official repos)
for pkg in "${WRONG_SOURCE_AUR[@]}"; do
fix "Reinstalling $pkg from AUR (was official repo)..."
# --aur flag ensures yay only looks in the AUR, not official repos
if yay -S --aur --noconfirm "$pkg"; then
ok "$pkg reinstalled from AUR"
FIXED=$((FIXED + 1))
@ -208,6 +287,7 @@ if [ "$ISSUES" -eq 0 ]; then
echo -e "${GREEN}${BOLD}All checks passed — no source mismatches or missing required packages.${RESET}"
else
echo -e "${RED}${BOLD}${ISSUES} issue(s) found — review items marked ✘ or ⚠ above.${RESET}"
# Only show the --fix hint if there are packages that can actually be auto-fixed
[ "$FIX" -eq 0 ] && [ $((${#WRONG_SOURCE_OFFICIAL[@]} + ${#WRONG_SOURCE_AUR[@]})) -gt 0 ] && \
echo -e "${YELLOW} Run with --fix to automatically reinstall wrong-source packages.${RESET}"
exit 1

View File

@ -1,27 +1,79 @@
#!/bin/bash
# ╔══════════════════════════════════════════════════════════════════════════════╗
# ║ install.sh — Legacy manual dotfiles installer ║
# ║ ║
# ║ PURPOSE: ║
# ║ The original, non-TUI entry point for setting up the dotfiles on an ║
# ║ already-running Arch Linux system. Runs modules sequentially with ║
# ║ minimal user interaction (a single DE selection prompt). ║
# ║ ║
# ║ USAGE: bash ~/Dotfiles/setup/install.sh ║
# ║ ║
# ║ SUPERSEDED BY: tui-install.sh / simple-install.sh (preferred) ║
# ║ This file is kept for quick headless runs or scripted CI deployments. ║
# ╚══════════════════════════════════════════════════════════════════════════════╝
set -uo pipefail
# -u: treat unset variables as errors (catches typos in variable names)
# -o pipefail: if any command in a pipeline fails, the whole pipeline fails
# (Note: -e is intentionally omitted so module failures don't abort the whole run)
# ── Logging setup ─────────────────────────────────────────────────────────────
# All output is tee'd to a log file so the run can be reviewed after the fact.
LOG="$HOME/dotfiles-install.log"
# Truncate the log file at the start of each run (> with no input empties it)
> "$LOG"
# Stamp the log with a timestamp for traceability
printf "Dotfiles install: %s\n" "$(date)" >> "$LOG"
# Redirect both stdout and stderr through tee so they appear on screen AND
# get appended to the log file simultaneously.
# exec > >(tee -a ...) 2>&1 is a permanent redirect for the rest of the script.
exec > >(tee -a "$LOG") 2>&1
# ── Core installation modules ──────────────────────────────────────────────────
# These three modules form the mandatory base layer and must run in this order:
# 1. package-managers — installs yay (AUR helper), nvm (Node.js), rustup (Rust)
# 2. core-packages — installs 100+ system packages via pacman/yay
# 3. core — enables systemd services (NetworkManager, cronie, greetd, etc.)
echo "Running Core installation Scripts"
bash ~/Dotfiles/setup/modules/package-managers.sh
bash ~/Dotfiles/setup/modules/core-packages.sh
bash ~/Dotfiles/setup/modules/core.sh
# ── Shell configuration deployment ────────────────────────────────────────────
# Deploys shell dotfiles (zsh, neovim, yazi, starship, etc.) and installs
# oh-my-zsh with its plugins. Run after core because it needs packages from core.
echo "Running Shell config deployment Script"
bash ~/Dotfiles/setup/modules/shell-setup.sh
# ── Desktop environment selection ─────────────────────────────────────────────
# Prompts for a single keypress to choose which DE installer to run.
# -n1: read exactly one character without needing Enter
# -p: display the prompt inline
read -n1 -p "what DE to install? [hyprland,sway,none]" doit
case $doit in
# HyprLua is the primary/recommended DE — Hyprland configured via Lua scripts
hyprland) bash ~/Dotfiles/setup/modules/Desktop-Environments/hyprland.sh ;;
# Sway — Wayland tiling compositor, i3-compatible
sway) bash ~/Dotfiles/setup/modules/Desktop-Environments/sway.sh ;;
# Skip DE entirely (for headless/server setups)
none) echo "Skipping DE installation" ;;
# Catch-all for invalid input
*) echo "please choose a desktop environment to install" ;;
esac
# ── Optional application modules ──────────────────────────────────────────────
# These are intentionally commented out — uncomment the ones you want.
# Each is a self-contained idempotent module that installs a specific app.
# In the TUI installer (tui-install.sh), these are presented as a checklist.
# Optional apps — uncomment what you want:
# bash ~/Dotfiles/setup/modules/optional-Modules/apps/steam.sh
# bash ~/Dotfiles/setup/modules/optional-Modules/apps/vesktop.sh
@ -37,4 +89,6 @@ esac
# bash ~/Dotfiles/setup/modules/optional-Modules/zfs.sh
# bash ~/Dotfiles/setup/modules/optional-Modules/wprs.sh
# ── Done ──────────────────────────────────────────────────────────────────────
# Print a final message pointing the user to the log file for review.
printf "\nDone. Log: %s\n" "$LOG"

View File

@ -1,8 +1,55 @@
#!/bin/bash
# =============================================================================
# cosmic.sh — COSMIC Desktop Environment installer
# =============================================================================
# Part of the Dotfiles setup system for Arch Linux.
#
# COSMIC is a Wayland-native desktop environment developed by System76 in Rust.
# Unlike traditional DEs that layer on top of existing WM infrastructure, COSMIC
# ships its own compositor (cosmic-comp), shell, and settings daemon as a
# tightly integrated suite.
#
# This script installs the full COSMIC suite via pacman, sets up the PipeWire
# audio stack, configures networking and Bluetooth, and enables the appropriate
# display manager (cosmic-greeter preferred, SDDM as fallback).
#
# Prerequisites: run as a normal user with sudo access; pacman must be available;
# the system should already be connected to the internet.
# =============================================================================
# Exit immediately on error; treat unset variables as errors; propagate pipe
# failures so that a failing command early in a pipeline does not go unnoticed.
set -euo pipefail
# Pull in the shared logging helpers (log, warn, err) from the setup library.
# BASH_SOURCE[0] resolves to this file's path even when the script is sourced
# from another directory, making the relative path to lib/ reliable.
source "$(dirname "${BASH_SOURCE[0]}")/../lib/logging.sh"
log "Installing COSMIC desktop..."
# Install the COSMIC meta-package plus supporting infrastructure:
#
# cosmic — the COSMIC desktop meta-package (pulls in cosmic-comp,
# cosmic-panel, cosmic-settings, cosmic-app-library, etc.)
#
# pipewire — modern audio/video routing daemon; replaces PulseAudio
# and JACK for most desktop workloads
# wireplumber — session/policy manager for PipeWire; decides which
# streams get routed where
# pipewire-alsa — ALSA compatibility shim so legacy ALSA apps use PipeWire
# pipewire-jack — JACK compatibility shim for professional audio software
# pipewire-pulse — PulseAudio socket emulation; keeps apps that speak
# libpulse working without changes
#
# networkmanager — connection management daemon; handles wired, Wi-Fi,
# VPN, and mobile broadband with a D-Bus API
#
# bluez — the Linux Bluetooth protocol stack
# bluez-utils — command-line tools (bluetoothctl) for managing devices
#
# flatpak — universal app sandboxing/delivery; lets COSMIC Software
# install Flathub apps without distro packaging
sudo pacman -S --noconfirm --needed \
cosmic \
pipewire wireplumber pipewire-alsa pipewire-jack pipewire-pulse \
@ -11,14 +58,30 @@ sudo pacman -S --noconfirm --needed \
flatpak
log "Enabling services..."
# COSMIC ships its own display manager (cosmic-greeter) built with iced.
# Prefer it over SDDM for a cohesive look-and-feel; fall back to SDDM when
# the package is not present (e.g. on systems where cosmic-greeter is still
# in the AUR or not yet stabilised on this arch build).
#
# pacman -Qi queries the local package database and exits non-zero when the
# package is not installed; &>/dev/null silences both stdout and stderr so
# the check is invisible to the user.
# cosmic-greeter is COSMIC's own display manager; fall back to sddm if absent
if pacman -Qi cosmic-greeter &>/dev/null; then
sudo systemctl enable cosmic-greeter.service
else
# SDDM (Simple Desktop Display Manager) is the widely used Qt-based DM;
# it supports both X11 and Wayland sessions and integrates well with COSMIC.
sudo pacman -S --noconfirm --needed sddm
sudo systemctl enable sddm.service
fi
# Enable NetworkManager so the network is managed on every boot.
sudo systemctl enable NetworkManager.service
# Enable the Bluetooth daemon so adapters are powered up on boot and devices
# can reconnect automatically.
sudo systemctl enable bluetooth.service
log "COSMIC installation complete. Reboot to start."

View File

@ -1,8 +1,56 @@
#!/bin/bash
# =============================================================================
# gnome.sh — GNOME Desktop Environment installer
# =============================================================================
# Part of the Dotfiles setup system for Arch Linux.
#
# GNOME is a mature, GTK4-based desktop environment that targets simplicity and
# a touch-friendly workflow. On Arch it is shipped as the 'gnome' group, which
# pulls in GNOME Shell (the compositor/WM), Mutter, GDM (the display manager),
# and the core GNOME application set.
#
# This script installs GNOME plus the PipeWire audio stack, GNOME's XDG portal
# (required for Flatpak sandboxing and screen sharing under Wayland), and
# enables the essential system services.
#
# Prerequisites: run as a normal user with sudo access; pacman available;
# internet connection active.
# =============================================================================
# Exit on any error; fail on unset variables; propagate pipeline errors.
set -euo pipefail
# Load shared logging helpers (log, warn, err) from the setup library.
source "$(dirname "${BASH_SOURCE[0]}")/../lib/logging.sh"
log "Installing GNOME desktop..."
# Install GNOME and its ecosystem:
#
# gnome — the official GNOME package group; includes
# gnome-shell, mutter, gdm, gnome-control-center,
# nautilus, and other core apps
#
# gnome-tweaks — exposes advanced settings not available in
# GNOME Settings (fonts, window buttons, legacy
# GTK theme overrides, extensions toggle)
#
# xdg-desktop-portal-gnome — GNOME's implementation of the XDG Desktop
# Portal D-Bus API; required for Flatpak apps to
# open file choosers, request screenshots, and
# share screens under Wayland
#
# pipewire — low-latency audio/video routing daemon
# wireplumber — PipeWire session/policy manager
# pipewire-alsa — ALSA compatibility layer on top of PipeWire
# pipewire-jack — JACK API emulation for pro-audio software
# pipewire-pulse — PulseAudio socket emulation for legacy apps
#
# networkmanager — network connection management daemon (wired,
# Wi-Fi, VPN, etc.)
#
# flatpak — sandboxed app delivery; GNOME Software uses it
# to install Flathub applications
sudo pacman -S --noconfirm --needed \
gnome \
gnome-tweaks \
@ -12,7 +60,12 @@ sudo pacman -S --noconfirm --needed \
flatpak
log "Enabling services..."
# GDM (GNOME Display Manager) handles the login screen and session startup.
# It is bundled with the 'gnome' group and is the canonical choice for GNOME.
sudo systemctl enable gdm.service
# Start NetworkManager on boot so connections are available before login.
sudo systemctl enable NetworkManager.service
log "GNOME installation complete. Reboot to start."

View File

@ -1,144 +1,405 @@
#!/bin/bash
# =============================================================================
# hyprland.sh — Hyprland Desktop Environment installer (legacy hyprlang config)
# =============================================================================
# Part of the Dotfiles setup system for Arch Linux.
#
# Hyprland is a dynamic tiling Wayland compositor built in C++ with a focus on
# animations, eye-candy, and a powerful plugin system (hyprpm).
#
# This is the LEGACY variant that uses hyprlang (.conf) configuration files.
# The newer hyprlua.sh uses Lua for configuration instead. Both installers
# share a nearly identical package list; the key differences are:
# - EWW and config source paths point to desktopenvs/hyprland/ (not hyprlua/)
# - hypr-usr/ device-specific .conf files are placed at ~/.config/ root level
# and sourced by hyprland.conf via 'source' directives
# - hyprlua.sh additionally installs python-opencv and v4l-utils for the
# webcam-based presence/idle detection daemon
#
# High-level install flow:
# 1. System update + Flatpak
# 2. pacman packages (compositor, audio, networking, tools, fonts)
# 3. systemd service enablement (NetworkManager, ly display manager, udisks2)
# 4. AUR packages via yay
# 5. EWW status-bar (interactive form-factor selection + Rust compilation)
# 6. Themes, icons, ly config, terminal symlink, SSH askpass
# 7. Nordzy left-hand cursor theme
# 8. Bluetooth / iwd wireless services
# 9. Config file deployment from Dotfiles
# 10. Wallpaper + resource files
# 11. Python venv for helper scripts
# 12. Udiskie tray-icon symlink fix
# 13. config-updater symlinks + apply-theme.sh
#
# Prerequisites: internet access, yay (AUR helper), rustup, git, wget, cargo.
# =============================================================================
# Exit immediately if any command fails — partial DE installs can leave the
# system in a broken graphical state, so aborting early is safer.
set -e
# Load shared logging helpers (log, warn, err) from the setup library.
source "$(dirname "${BASH_SOURCE[0]}")/../lib/logging.sh"
log "Starting Hyprland installer (legacy — hyprlang config)..."
# ---------------------------------------------------------------------------
# 1. Update system and install Flatpak
# ---------------------------------------------------------------------------
# Run a full system upgrade first to avoid Arch partial-upgrade issues (on a
# rolling release, installing new packages against an un-updated system can
# cause library ABI mismatches). Flatpak is installed early so AUR steps that
# depend on it can find it.
log "Updating system and installing Flatpak..."
sudo pacman -Syu --noconfirm --needed flatpak
# ---------------------------------------------------------------------------
# 2. Install required packages
# ---------------------------------------------------------------------------
log "Installing required packages..."
sudo pacman -Syu --noconfirm --needed \
hyprland hyprcursor wl-clipboard hyprpaper hyprlock wofi kitty dunst \
# hyprland — the Wayland compositor / window manager
# hyprcursor — hardware-accelerated cursor rendering for Hyprland
# wl-clipboard — wl-copy / wl-paste; clipboard tools for Wayland
# hyprpaper — wallpaper utility with per-monitor and animation support
# hyprlock — GPU-accelerated screen locker (Hyprland-native)
# wofi — Wayland application launcher (rofi replacement)
# kitty — GPU-accelerated terminal (default terminal in this setup)
# dunst — lightweight, scriptable notification daemon
nwg-dock-hyprland nwg-drawer nwg-menu nwg-look \
# nwg-dock-hyprland — dock/taskbar with Hyprland workspace awareness
# nwg-drawer — application drawer (grid-style launcher)
# nwg-menu — GTK menu used in the panel's app button
# nwg-look — GTK/cursor/icon theme picker for wlroots sessions
python cmake meson cpio pkgconf ruby-pkg-config \
# Build tools needed to compile EWW (Rust) and AUR packages (C/C++/Ruby)
hyprsunset hypridle ksshaskpass \
# hyprsunset — blue-light filter / night-mode daemon for Hyprland
# hypridle — idle daemon that triggers lock/suspend after inactivity
# ksshaskpass — Qt SSH passphrase dialog (registered as ssh-askpass)
nm-connection-editor network-manager-applet blueman bluez \
# nm-connection-editor — GTK GUI for editing NetworkManager profiles
# network-manager-applet — system-tray applet showing Wi-Fi/VPN status
# blueman — GTK Bluetooth manager with tray icon
# bluez — core Linux Bluetooth protocol stack
pipewire alsa-utils firefox greetd-tuigreet \
# pipewire — modern audio/video routing daemon
# alsa-utils — ALSA CLI tools (amixer, aplay) for low-level audio
# firefox — web browser
# greetd-tuigreet — TUI login greeter (text-based, no GPU needed at DM)
grim slurp gst-plugin-pipewire imagemagick \
# grim — Wayland screenshot tool (outputs to file or stdout)
# slurp — interactive region selector (used with grim)
# gst-plugin-pipewire — GStreamer plugin bridging through PipeWire
# imagemagick — image processing (screenshot effects, compositing)
nerd-fonts otf-font-awesome \
# nerd-fonts — all Nerd Font patched families (icon glyphs in bar)
# otf-font-awesome — Font Awesome icon glyphs for EWW/dunst
pipewire-alsa pipewire-jack pipewire-pulse \
# Compatibility layers: let ALSA, JACK, and PulseAudio apps route through
# PipeWire without requiring code changes in those apps
qt5-wayland qt6-wayland swww ttf-jetbrains-mono wireplumber \
# qt5-wayland / qt6-wayland — Qt platform plugins for native Wayland rendering
# swww — smooth animated wallpaper daemon
# ttf-jetbrains-mono — monospace font for terminals and the EWW bar
# wireplumber — PipeWire session/policy manager
qt6ct xdg-desktop-portal-hyprland xdg-utils \
# qt6ct — Qt6 colour/style configurator (run as user)
# xdg-desktop-portal-hyprland — Hyprland XDG portal (screen share, file pick)
# xdg-utils — xdg-open / MIME handling helpers
xorg-server xorg-xinit papirus-icon-theme \
# xorg-server / xorg-xinit — X server for XWayland (legacy X11 apps)
# papirus-icon-theme — icon theme used system-wide and by udiskie
cool-retro-term qalculate-gtk iwd dbus \
# cool-retro-term — retro CRT-style terminal emulator
# qalculate-gtk — powerful GTK calculator with unit conversion
# iwd — Intel Wireless Daemon (lightweight Wi-Fi back-end)
# dbus — D-Bus inter-process communication daemon
thunar tumbler thunar-archive-plugin thunar-shares-plugin thunar-volman \
# thunar — lightweight GTK file manager (XFCE project)
# tumbler — thumbnail service for Thunar
# thunar-archive-plugin — right-click archive (zip/tar) integration
# thunar-shares-plugin — Samba share management from Thunar
# thunar-volman — automatic volume/device mounting in Thunar
hyprpicker pcmanfm-qt udisks2 ly kew \
# hyprpicker — colour picker outputting hex/rgb/hsl under Hyprland
# pcmanfm-qt — Qt file manager kept as fallback
# udisks2 — D-Bus service for querying and managing storage devices
# ly — minimal TUI display manager (replaces getty on tty1)
# kew — terminal music player
hyprpolkitagent pavucontrol playerctl wf-recorder sound-theme-freedesktop
# hyprpolkitagent — Hyprland-native Polkit authentication agent
# pavucontrol — PulseAudio/PipeWire volume control GUI
# playerctl — MPRIS media player controller (play/pause CLI)
# wf-recorder — screen recorder for wlroots compositors
# sound-theme-freedesktop — standard freedesktop sound event library
# ---------------------------------------------------------------------------
# 3. Enable essential services
# ---------------------------------------------------------------------------
log "Enabling essential services..."
# NetworkManager must be active on boot for network connectivity.
sudo systemctl enable NetworkManager.service
# getty@tty1 is the default text-mode login prompt; we replace it with ly.
# '|| true' prevents abort if the unit is already disabled or doesn't exist.
sudo systemctl disable getty@tty1.service || true
# ly is the TUI display manager that runs on tty1 and launches Hyprland after
# the user logs in.
sudo systemctl enable ly@tty1.service
# udisks2 provides the D-Bus API for block devices; required by udiskie for
# automatic USB/external drive mounting.
sudo systemctl enable udisks2.service
# ---------------------------------------------------------------------------
# 4. Install AUR packages
# ---------------------------------------------------------------------------
log "Installing AUR packages..."
# Activate the stable Rust toolchain so AUR packages that compile Rust code
# (and EWW compiled below) use a known-good toolchain version.
rustup default stable
# yay flags:
# --answerdiff None — skip PKGBUILD diff display (non-interactive)
# --answerclean All — always clean build dirs between builds
# --noconfirm — no confirmation prompts
#
# Packages:
# hyprland-workspaces — EWW workspace widget reading Hyprland IPC socket
# vicinae-bin — pre-built vicinae (vim-like keybind layer for scripts)
# bluetuith — TUI Bluetooth manager (used in the menus)
# wvkbd — on-screen virtual keyboard for Wayland (touch)
# iwmenu — wofi-based Wi-Fi network picker using iwd
# pinta — simple image editor
# walker-bin — fast app launcher / runner (pre-built binary)
# ulauncher — extensible app launcher with plugin ecosystem
# bzmenu — quick Bluetooth device connect menu for the bar
# udiskie — automounter with tray icon (built on udisks2)
# wofi-calc — inline calculator pop-up inside wofi
# bri — brightness control CLI helper for bar widgets
# chamel — theme-switching helper (applies colour palettes)
yay -Syu --answerdiff None --answerclean All --noconfirm \
hyprland-workspaces vicinae-bin bluetuith wvkbd iwmenu pinta \
walker-bin ulauncher bzmenu udiskie \
wofi-calc bri chamel
# ---------------------------------------------------------------------------
# 5. EWW bar selection and compilation
# ---------------------------------------------------------------------------
# EWW (Elkowar's Wacky Widgets) is a Rust widget framework that powers the
# custom status bar. Three layouts exist for different hardware form factors:
# eww/ — Notebook layout (includes battery widget)
# eww-nobattery/ — Desktop PC layout (no battery bar)
# eww-touch/ — Tablet layout (larger touch targets)
log "Setting up EWW bar..."
# Wipe any previous EWW config to avoid stale widget definitions conflicting
# with the freshly copied one.
rm -rf ~/.config/eww
# Prompt for form factor with a single keypress (no Enter needed).
read -n1 -p "Install eww bar for PC, Notebook or Tablet [P/N/T]: " doit
echo
echo # Print newline so subsequent output starts on a fresh line
case $doit in
# Notebook: copy the battery-aware layout
n|N) cp -rf ~/Dotfiles/desktopenvs/hyprland/eww/ ~/.config/ ;;
# Desktop PC: copy the no-battery layout
p|P) cp -rf ~/Dotfiles/desktopenvs/hyprland/eww-nobattery/ ~/.config/eww ;;
# Tablet: copy touch-optimised layout
t|T) cp -rf ~/Dotfiles/desktopenvs/hyprland/eww-touch/ ~/.config/eww ;;
# Unknown key: warn and skip; the user must copy manually later
*) warn "No valid choice — skipping EWW copy. Run manually later." ;;
esac
log "Compiling EWW..."
# EWW is not in the official repos so we compile from source.
# ~/install-tmp is a staging area for source trees fetched during installation.
mkdir -p ~/install-tmp
cd ~/install-tmp
git clone https://github.com/elkowar/eww
cd eww
# --release → optimised binary (significantly faster than debug)
# --no-default-features → disable the X11 backend entirely
# --features=wayland → enable only the Wayland backend
cargo build --release --no-default-features --features=wayland
chmod +x target/release/eww
# Install system-wide so any autostart entry or script can call 'eww' directly.
sudo cp target/release/eww /usr/bin/
cd ~
cd ~ # Return home so subsequent relative-path steps work correctly
# ---------------------------------------------------------------------------
# 6. Theme and icon setup
# ---------------------------------------------------------------------------
log "Installing themes and icons..."
# Install the cyberqueer GTK theme system-wide so GTK2/3/4 apps pick it up.
sudo cp -r ~/Dotfiles/gtk-themes/cyberqueer /usr/share/themes
# Install the matching btop colour theme for the terminal system monitor.
sudo cp ~/Dotfiles/desktopenvs/hyprland/btop/themes/cyberqueer.theme /usr/share/btop/themes
# Deploy the ly display-manager config (login screen appearance, session list).
sudo cp -f ~/Dotfiles/etc-ly-config.ini /etc/ly/config.ini
# Register kitty as the system-default terminal for xdg-terminal-exec so
# file managers and scripts that call "Open Terminal Here" launch kitty.
sudo ln -sf /usr/bin/kitty /usr/bin/xdg-terminal-exec
# Register ksshaskpass as the SSH passphrase helper.
# When SSH needs a passphrase in a non-terminal context (e.g. git push from a
# script), it invokes /usr/lib/ssh/ssh-askpass; this link provides a GUI dialog.
sudo ln -sf /usr/bin/ksshaskpass /usr/lib/ssh/ssh-askpass
# ---------------------------------------------------------------------------
# 7. Cursor setup
# ---------------------------------------------------------------------------
# Nordzy-cursors-lefthand is a Nord-palette left-handed cursor theme.
# Downloaded from GitHub Releases rather than the AUR because the AUR build
# may lag behind the upstream release schedule.
log "Installing cursor theme..."
mkdir -p ~/.icons
wget -O ~/install-tmp/Nordzy-cursors-lefthand.tar.gz \
https://github.com/guillaumeboehm/Nordzy-cursors/releases/download/v2.3.0/Nordzy-cursors-lefthand.tar.gz
# Extract directly into ~/.icons/ so the theme is immediately discoverable by
# Hyprland (via hyprcursor) and by GTK applications.
tar -zxf ~/install-tmp/Nordzy-cursors-lefthand.tar.gz -C ~/.icons/
# ---------------------------------------------------------------------------
# 8. Enable Bluetooth and wireless services
# ---------------------------------------------------------------------------
log "Enabling Bluetooth and wireless services..."
# bluez: core Bluetooth protocol stack (powers and manages adapters on boot)
sudo systemctl enable bluez
# bluetooth.service: high-level service handling pairing and profiles
sudo systemctl enable bluetooth.service
# iwd: modern Wi-Fi daemon from Intel; used by iwmenu for the bar Wi-Fi picker
sudo systemctl enable iwd.service
# 9. Hyprland plugins — must be run from inside a live Hyprland session
# Run manually after first login:
# ---------------------------------------------------------------------------
# 9. Hyprland plugins (must be run from inside a live Hyprland session)
# ---------------------------------------------------------------------------
# hyprpm links against the exact compositor binary, so it cannot run from the
# installer. Execute these commands manually after first login:
# hyprpm update
# hyprpm add https://github.com/hyprwm/hyprland-plugins
# ---------------------------------------------------------------------------
# 10. Copy configs
# ---------------------------------------------------------------------------
log "Copying configs..."
# Each entry in CONFIGS maps to a sub-directory (or file) inside
# desktopenvs/hyprland/ that is deployed verbatim to ~/.config/.
# Old copies are wiped first to prevent stale files from prior installs
# interfering with the fresh deployment.
CONFIGS=(kitty mimeapps.list vicinae walker ulauncher hypr xfce4 wofi dunst alacritty nwg-dock-hyprland nwg-drawer nwg-panel scripts btop gtk-3.0)
for cfg in "${CONFIGS[@]}"; do
rm -rf ~/.config/"$cfg"
cp -r ~/Dotfiles/desktopenvs/hyprland/"$cfg" ~/.config/
done
# hypr-usr/ holds device-specific overrides (monitor layout, keybinds, envvars).
# In the legacy hyprlang setup these are .conf files at the ~/.config/ root
# level; hyprland.conf sources them with 'source = ~/.config/monitors.conf' etc.
cp ~/Dotfiles/desktopenvs/hyprland/hypr-usr/* ~/.config/
# colors.conf defines the shared colour palette used by EWW, dunst, and scripts.
cp ~/Dotfiles/colors.conf ~/.config/colors.conf
# ---------------------------------------------------------------------------
# 11. Wallpaper and resources
# ---------------------------------------------------------------------------
log "Copying wallpaper and resources..."
mkdir -p ~/Pictures
# Firefox logo SVG used as the dock/launcher shortcut icon.
cp ~/Dotfiles/resources/fflogo.svg ~/Pictures/fflogo.svg
# Download the personalised wallpaper from the owner's Nextcloud instance.
# ?v=15 is a cache-buster version parameter on the Nextcloud theming API.
wget "https://cloud.abdelbaki.eu/apps/theming/image/background?v=15" -O ~/Pictures/background.jpg
# ---------------------------------------------------------------------------
# 12. Python venv for scripts
# ---------------------------------------------------------------------------
# Many EWW and Hyprland helper scripts are written in Python and need these
# third-party libraries. An isolated venv avoids polluting the system Python
# and prevents conflicts with pacman-managed Python packages.
log "Setting up Python venv for scripts..."
python -m venv ~/.config/python-script
~/.config/python-script/bin/pip install speedtest-cli requests pint simpleeval parsedatetime
~/.config/python-script/bin/pip install \
speedtest-cli `# Internet speed test widget for the EWW bar` \
requests `# HTTP client used by various widgets and scripts` \
pint `# Unit conversion library for the calculator script` \
simpleeval `# Safe maths expression evaluator (wofi-calc backend)` \
parsedatetime `# Natural-language date/time parser for timer scripts`
# ---------------------------------------------------------------------------
# 13. Udiskie icon fix
# ---------------------------------------------------------------------------
# udiskie uses freedesktop icon names (udiskie-checkbox-checked, etc.) that
# most icon themes do not ship. We symlink them from Papirus-Dark's existing
# checkbox icons as a close visual match. Placing symlinks in the hicolor
# theme ensures every GTK app can find them as hicolor is the last-resort
# fallback in the icon theme lookup chain.
log "Applying Udiskie icon fix..."
PAPIRUS_DIR="/usr/share/icons/Papirus-Dark/status"
HICOLOR_DIR="/usr/share/icons/hicolor/scalable/status"
if [ -d "$PAPIRUS_DIR" ]; then
sudo ln -sf "$PAPIRUS_DIR/checkbox-checked.svg" "$HICOLOR_DIR/udiskie-checkbox-checked.svg"
sudo ln -sf "$PAPIRUS_DIR/checkbox-unchecked.svg" "$HICOLOR_DIR/udiskie-checkbox-unchecked.svg"
# Rebuild the icon cache so GTK discovers the new symlinked entries without
# requiring a full logout/login.
sudo gtk-update-icon-cache -f -t /usr/share/icons/hicolor
else
warn "Papirus-Dark not found — skipping udiskie icon fix."
fi
# ---------------------------------------------------------------------------
# 14. Enable udiskie
# ---------------------------------------------------------------------------
# udiskie is the auto-mounter / tray-icon daemon that surfaces mounted volumes.
# Enable the systemd user unit and start it immediately so this session already
# benefits from auto-mounting without requiring a reboot.
log "Enabling udiskie service..."
sudo systemctl enable udiskie.service
sudo systemctl start udiskie.service
# ---------------------------------------------------------------------------
# 15. Install config updater and theme script
# ---------------------------------------------------------------------------
# config-updater is a lightweight mechanism that re-syncs selected config files
# from the Dotfiles repo without running the full installer again.
# updater.conf lists which files to keep in sync; update-configs.sh performs
# the actual copy operation.
#
# Symlinks (ln -sf) are used so that 'git pull' in ~/Dotfiles immediately takes
# effect the next time update-configs.sh is run.
log "Installing config updater and theme script..."
mkdir -p ~/.config/config-updater
ln -sf ~/Dotfiles/desktopenvs/hyprland/config-updater/updater.conf ~/.config/config-updater/updater.conf
ln -sf ~/Dotfiles/desktopenvs/hyprland/config-updater/update-configs.sh ~/update-configs.sh
# apply-theme.sh applies the cyberqueer colour palette across all running apps
# (sets GTK/Qt themes, reloads dunst config, refreshes EWW colour variables).
# Copied (not symlinked) to ~ so it works even if ~/Dotfiles is unmounted.
cp ~/Dotfiles/apply-theme.sh ~/apply-theme.sh
chmod +x ~/apply-theme.sh
# ---------------------------------------------------------------------------
# 16. WallRizz (run manually after login — requires a running desktop session)
# ---------------------------------------------------------------------------
# WallRizz generates a colour scheme from the active wallpaper and applies it
# to EWW, dunst, and terminals. It requires a live Wayland session and cannot
# be safely run from this installer script.
# Uncomment and run manually after your first login:
# curl -sL "$(curl -s https://api.github.com/repos/5hubham5ingh/WallRizz/releases/latest \
# | grep -Po '"browser_download_url": "\K[^"]+' | grep WallRizz)" | tar -xz \
# && sudo mv WallRizz /usr/bin/

View File

@ -1,147 +1,407 @@
#!/bin/bash
# =============================================================================
# hyprlua.sh — HyprLua Desktop Environment installer (Lua-based config)
# =============================================================================
# Part of the Dotfiles setup system for Arch Linux.
#
# HyprLua is the primary / recommended Hyprland variant in this Dotfiles repo.
# It uses a Lua configuration file (hyprland.lua) instead of hyprlang (.conf),
# enabling programmatic config generation, per-device overrides via Lua modules,
# and more maintainable keybind/rule definitions.
#
# Differences from hyprland.sh (the legacy .conf variant):
# - Config source paths point to desktopenvs/hyprlua/
# - Device-specific overrides live in ~/.config/hypr/usr/ (Lua modules
# loaded via require("usr.*")), not at ~/.config/ root
# - Adds python-opencv + v4l-utils for the webcam presence-detection daemon
# - Tablet mode also installs evdev-right-click-emulation from AUR
# - gsettings sets prefer-dark globally (relevant for GTK apps under Hyprland)
# - qt6ct is NOT installed (hyprlua dropped it from the package list)
# - Adds --needed flag to yay to skip already-installed AUR packages
#
# High-level install flow:
# 1. System update + Flatpak
# 2. pacman packages (compositor, audio, networking, tools, fonts, webcam)
# 3. systemd service enablement
# 4. AUR packages via yay
# 5. EWW bar (form-factor selection + Rust compilation)
# 6. Themes, ly config, terminal symlink, SSH askpass, dark-mode preference
# 7. Nordzy left-hand cursor theme
# 8. Bluetooth / iwd services
# 9. Config file deployment (hyprlua source tree)
# 10. Wallpaper + resource files
# 11. Python venv for helper scripts
# 12. Udiskie tray-icon symlink fix
# 13. config-updater symlinks + apply-theme.sh
#
# Prerequisites: internet access, yay (AUR helper), rustup, git, wget, cargo.
# =============================================================================
# Exit immediately on any error — partial DE installs leave a broken system.
set -e
# Load shared logging helpers (log, warn, err) from the setup library.
source "$(dirname "${BASH_SOURCE[0]}")/../lib/logging.sh"
log "Starting HyprLua installer (Lua-based config)..."
# ---------------------------------------------------------------------------
# 1. Update system and install Flatpak
# ---------------------------------------------------------------------------
# Full system upgrade before package installs to avoid Arch partial-upgrade
# issues (library ABI mismatches between un-upgraded and newly installed pkgs).
log "Updating system and installing Flatpak..."
sudo pacman -Syu --noconfirm --needed flatpak
# ---------------------------------------------------------------------------
# 2. Install required packages
# ---------------------------------------------------------------------------
log "Installing required packages..."
sudo pacman -Syu --noconfirm --needed \
hyprland hyprcursor wl-clipboard hyprpaper hyprlock wofi kitty dunst \
# hyprland — Wayland compositor / window manager
# hyprcursor — hardware-accelerated cursor rendering for Hyprland
# wl-clipboard — wl-copy / wl-paste; Wayland clipboard CLI tools
# hyprpaper — wallpaper utility with per-monitor support
# hyprlock — GPU-accelerated screen locker (Hyprland-native)
# wofi — Wayland application launcher (rofi alternative)
# kitty — GPU-accelerated terminal (default in this setup)
# dunst — lightweight, scriptable notification daemon
nwg-dock-hyprland nwg-drawer nwg-menu nwg-look \
# nwg-dock-hyprland — dock/taskbar with Hyprland workspace awareness
# nwg-drawer — grid application drawer / launcher
# nwg-menu — GTK application menu for the panel button
# nwg-look — GTK/cursor/icon theme picker for wlroots sessions
python cmake meson cpio pkgconf ruby-pkg-config \
# Build toolchain required for EWW (Rust) and AUR package compilation
hyprsunset hypridle ksshaskpass \
# hyprsunset — blue-light filter / night-mode daemon
# hypridle — idle daemon triggering lock/suspend after inactivity
# ksshaskpass — Qt SSH passphrase dialog registered as ssh-askpass
nm-connection-editor network-manager-applet blueman bluez \
# nm-connection-editor — GTK editor for NetworkManager connection profiles
# network-manager-applet — tray applet showing network connection status
# blueman — GTK Bluetooth manager with tray icon support
# bluez — core Linux Bluetooth protocol stack
pipewire alsa-utils firefox greetd-tuigreet \
# pipewire — modern audio/video routing daemon (replaces PulseAudio)
# alsa-utils — low-level ALSA CLI tools (amixer, aplay, arecord)
# firefox — web browser
# greetd-tuigreet — TUI login greeter (text-based, no GPU requirement)
grim slurp gst-plugin-pipewire imagemagick \
# grim — Wayland screenshot capture tool
# slurp — interactive rectangular region selector for screenshots
# gst-plugin-pipewire — GStreamer plugin routing through PipeWire
# imagemagick — image processing toolkit for screenshot effects
nerd-fonts otf-font-awesome \
# nerd-fonts — all Nerd Font families (icon glyphs in EWW bar)
# otf-font-awesome — Font Awesome glyph set for bar and notifications
pipewire-alsa pipewire-jack pipewire-pulse \
# Compatibility shims letting ALSA, JACK, and PulseAudio apps route through
# PipeWire transparently without needing to be recompiled
qt5-wayland qt6-wayland swww ttf-jetbrains-mono wireplumber \
# qt5-wayland / qt6-wayland — Qt Wayland platform plugins for native rendering
# swww — smooth animated wallpaper daemon for wlroots
# ttf-jetbrains-mono — monospace coding font used in terminals/bar
# wireplumber — PipeWire session and policy manager
xdg-desktop-portal-hyprland xdg-utils \
# xdg-desktop-portal-hyprland — Hyprland XDG portal (screen share, file pick)
# xdg-utils — xdg-open and MIME handling utilities
# Note: qt6ct is NOT included in hyprlua (differs from hyprland.sh)
xorg-server xorg-xinit papirus-icon-theme \
# xorg-server / xorg-xinit — X server for XWayland (legacy X11 apps)
# papirus-icon-theme — icon theme used system-wide and by udiskie
cool-retro-term qalculate-gtk iwd dbus \
# cool-retro-term — retro CRT-style terminal emulator
# qalculate-gtk — full-featured GTK calculator with unit conversion
# iwd — Intel Wireless Daemon (lighter Wi-Fi stack than wpa_supplicant)
# dbus — D-Bus inter-process communication daemon
thunar tumbler thunar-archive-plugin thunar-shares-plugin thunar-volman \
# thunar — lightweight GTK file manager
# tumbler — D-Bus thumbnail service for Thunar
# thunar-archive-plugin — archive (zip/tar) right-click integration
# thunar-shares-plugin — Samba share management in Thunar
# thunar-volman — automatic volume/device mounting in Thunar
hyprpicker pcmanfm-qt udisks2 ly kew \
# hyprpicker — colour picker outputting hex/rgb/hsl values
# pcmanfm-qt — Qt file manager (kept as fallback / alternative)
# udisks2 — D-Bus daemon for querying and managing storage devices
# ly — minimal TUI display manager (takes over tty1 from getty)
# kew — terminal-based music player
hyprpolkitagent pavucontrol playerctl wf-recorder sound-theme-freedesktop \
# hyprpolkitagent — Hyprland-native Polkit authentication agent
# pavucontrol — PulseAudio/PipeWire GUI volume mixer
# playerctl — MPRIS media player CLI controller
# wf-recorder — screen recorder for wlroots-based compositors
# sound-theme-freedesktop — standard freedesktop sound event samples
python-opencv v4l-utils
# python-opencv — OpenCV Python bindings; used by the webcam presence-
# detection daemon to watch for the user at the desk and
# inhibit idle/screen-lock while they are present
# v4l-utils — Video4Linux2 tools for enumerating and configuring webcams
# ---------------------------------------------------------------------------
# 3. Enable essential services
# ---------------------------------------------------------------------------
log "Enabling essential services..."
# NetworkManager: manages all network connections (wired, Wi-Fi, VPN).
sudo systemctl enable NetworkManager.service
# Disable the default getty login prompt on tty1 so ly can own that TTY.
# '|| true' prevents abort if the unit is already disabled.
sudo systemctl disable getty@tty1.service || true
# ly: TUI display manager that presents the login screen on tty1.
sudo systemctl enable ly@tty1.service
# udisks2: D-Bus block-device service required by udiskie for auto-mounting.
sudo systemctl enable udisks2.service
# ---------------------------------------------------------------------------
# 4. Install AUR packages
# ---------------------------------------------------------------------------
log "Installing AUR packages..."
# Pin the stable Rust toolchain so EWW and AUR Rust packages compile cleanly.
rustup default stable
# yay flags:
# --answerdiff None — skip PKGBUILD diff display (non-interactive)
# --answerclean All — always clean build directories
# --noconfirm — suppress all confirmation prompts
# --needed — skip packages that are already up-to-date (added vs
# hyprland.sh to avoid redundant AUR rebuilds)
#
# Packages:
# hyprland-workspaces — EWW workspace widget (reads Hyprland IPC)
# vicinae-bin — pre-built vicinae (vim-modal keybind layer)
# bluetuith — TUI Bluetooth manager (text-based device control)
# wvkbd — on-screen virtual keyboard for Wayland (tablet use)
# iwmenu — wofi/dmenu Wi-Fi picker driven by iwd
# pinta — accessible image editor (Paint.NET-like)
# walker-bin — fast application launcher (pre-built binary)
# ulauncher — extensible launcher with plugin support
# bzmenu — quick Bluetooth connect menu for the bar
# udiskie — auto-mounter with tray icon (built on udisks2)
# wofi-calc — inline calculator inside wofi
# bri — brightness control helper for bar widgets
# chamel — colour-palette / theme switcher
yay -Syu --answerdiff None --answerclean All --noconfirm --needed \
hyprland-workspaces vicinae-bin bluetuith wvkbd iwmenu pinta \
walker-bin ulauncher bzmenu udiskie \
wofi-calc bri chamel hyprmoncfg
# ---------------------------------------------------------------------------
# 5. EWW bar selection and compilation
# ---------------------------------------------------------------------------
# EWW (Elkowar's Wacky Widgets) powers the custom status bar.
# Three form-factor layouts ship in the Dotfiles:
# eww/ — Notebook: includes battery percentage widget
# eww-nobattery/ — Desktop PC: no battery widget
# eww-touch/ — Tablet: larger touch targets + right-click emulation
log "Setting up EWW bar..."
# Remove any existing EWW config to avoid leftover widget definitions.
rm -rf ~/.config/eww
# Single-keystroke form-factor selection (no Enter required).
read -n1 -p "Install eww bar for PC, Notebook or Tablet [P/N/T]: " doit
echo
echo # Newline after single-char read
case $doit in
# Notebook: battery-aware layout
n|N) cp -rf ~/Dotfiles/desktopenvs/hyprlua/eww/ ~/.config/ ;;
# Desktop PC: no battery widget
p|P) cp -rf ~/Dotfiles/desktopenvs/hyprlua/eww-nobattery/ ~/.config/eww ;;
# Tablet: touch layout + right-click emulation daemon
t|T) cp -rf ~/Dotfiles/desktopenvs/hyprlua/eww-touch/ ~/.config/eww
# evdev-right-click-emulation enables long-press → right-click on touch
# screens; its systemd service must be started alongside the session.
yay -S evdev-right-click-emulation
sudo systemctl enable --now evdev-rce.service ;;
# Unrecognised key: leave ~/.config/eww absent; user must copy manually.
*) warn "No valid choice — skipping EWW copy. Run manually later." ;;
esac
log "Compiling EWW..."
# EWW is not packaged in the official repos; we compile from source.
# ~/install-tmp is the staging directory for source trees fetched during install.
mkdir -p ~/install-tmp
cd ~/install-tmp
git clone https://github.com/elkowar/eww
cd eww
# --release → produce an optimised binary (noticeably faster UI)
# --no-default-features → exclude the X11 backend (we only target Wayland)
# --features=wayland → enable the Wayland back-end explicitly
cargo build --release --no-default-features --features=wayland
chmod +x target/release/eww
# Install system-wide so autostart entries and scripts can invoke 'eww' directly.
sudo cp target/release/eww /usr/bin/
cd ~
cd ~ # Return home so subsequent relative paths resolve correctly
# ---------------------------------------------------------------------------
# 6. Theme and icon setup
# ---------------------------------------------------------------------------
log "Installing themes and icons..."
# Install the cyberqueer GTK theme system-wide for GTK2/3/4 application styling.
sudo cp -r ~/Dotfiles/gtk-themes/cyberqueer /usr/share/themes
# Install the matching btop resource-monitor colour theme.
sudo cp ~/Dotfiles/desktopenvs/hyprlua/btop/themes/cyberqueer.theme /usr/share/btop/themes
# Deploy the ly display-manager configuration (login screen appearance).
sudo cp -f ~/Dotfiles/etc-ly-config.ini /etc/ly/config.ini
# Register kitty as the system terminal for xdg-terminal-exec so file managers
# and scripts that open "a terminal here" consistently launch kitty.
sudo ln -sf /usr/bin/kitty /usr/bin/xdg-terminal-exec
# Register ksshaskpass as the SSH passphrase GUI helper.
# SSH clients look for /usr/lib/ssh/ssh-askpass when they need a passphrase
# but have no controlling terminal available (e.g. git push from a bar widget).
sudo ln -sf /usr/bin/ksshaskpass /usr/lib/ssh/ssh-askpass
# Qt theming via qt-themes/cyberqueer is commented out (kept for reference);
# the Qt5 / Qt6 colour scheme is currently handled via qt6ct manual setup.
#bash ~/Dotfiles/qt-themes/cyberqueer/enable.sh
# Instruct GTK apps that prefer the system colour-scheme to use dark mode.
# This is particularly important under Hyprland where there is no GNOME
# settings daemon propagating the user's preference automatically.
gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark'
# ---------------------------------------------------------------------------
# 7. Cursor setup
# ---------------------------------------------------------------------------
# Nordzy-cursors-lefthand is a Nord-palette left-handed cursor theme.
# Downloaded directly from GitHub Releases for a pinned version; the AUR
# build can lag behind upstream.
log "Installing cursor theme..."
mkdir -p ~/.icons
wget -O ~/install-tmp/Nordzy-cursors-lefthand.tar.gz \
https://github.com/guillaumeboehm/Nordzy-cursors/releases/download/v2.3.0/Nordzy-cursors-lefthand.tar.gz
# Extract into ~/.icons/ so both Hyprland (hyprcursor) and GTK apps can find it.
tar -zxf ~/install-tmp/Nordzy-cursors-lefthand.tar.gz -C ~/.icons/
# ---------------------------------------------------------------------------
# 8. Enable Bluetooth and wireless services
# ---------------------------------------------------------------------------
log "Enabling Bluetooth and wireless services..."
# bluez: core Bluetooth daemon; powers adapters and makes them available on boot
sudo systemctl enable bluez
# bluetooth.service: handles pairing, profiles, and device reconnection
sudo systemctl enable bluetooth.service
# iwd: Intel Wireless Daemon; the Wi-Fi back-end used by iwmenu in the bar
sudo systemctl enable iwd.service
systemctl --user enable --now hyprmoncfgd
# 9. Hyprland plugins — must be run from inside a live Hyprland session
# Run manually after first login:
# ---------------------------------------------------------------------------
# 9. Hyprland plugins (must be run inside a live Hyprland session)
# ---------------------------------------------------------------------------
# hyprpm links against the exact running compositor binary, so it cannot be
# invoked from the installer. After first login, run:
# hyprpm update
# hyprpm add https://github.com/hyprwm/hyprland-plugins
# ---------------------------------------------------------------------------
# 10. Copy configs
# ---------------------------------------------------------------------------
log "Copying configs..."
# Deploy each config directory from the hyprlua Dotfiles source.
# The wipe-then-copy pattern ensures no stale files from older installs remain.
CONFIGS=(kitty mimeapps.list vicinae walker ulauncher hypr xfce4 wofi dunst alacritty nwg-dock-hyprland nwg-drawer nwg-panel scripts btop gtk-3.0)
for cfg in "${CONFIGS[@]}"; do
rm -rf ~/.config/"$cfg"
cp -r ~/Dotfiles/desktopenvs/hyprlua/"$cfg" ~/.config/
done
# hypr/usr/ (device-specific) is already inside hypr/ and copied above.
# Customise ~/.config/hypr/usr/ per device after install.
# In hyprlua, device-specific overrides (monitors, keybinds, autostart, etc.)
# live inside ~/.config/hypr/usr/ as Lua modules loaded by hyprland.lua via
# require("usr.*"). The usr/ directory is already bundled inside the hypr/
# directory copied above, so no separate step is needed here.
# After install, customise ~/.config/hypr/usr/ per device as needed.
# Shared colour palette used by EWW bar, dunst, and scripts.
cp ~/Dotfiles/colors.conf ~/.config/colors.conf
# ---------------------------------------------------------------------------
# 11. Wallpaper and resources
# ---------------------------------------------------------------------------
log "Copying wallpaper and resources..."
mkdir -p ~/Pictures
# Firefox logo SVG for the dock/launcher shortcut icon.
cp ~/Dotfiles/resources/fflogo.svg ~/Pictures/fflogo.svg
# Download the personalised wallpaper from the owner's Nextcloud instance.
# ?v=15 is a cache-buster on the Nextcloud theming background API endpoint.
wget "https://cloud.abdelbaki.eu/apps/theming/image/background?v=15" -O ~/Pictures/background.jpg
# ---------------------------------------------------------------------------
# 12. Python venv for scripts
# ---------------------------------------------------------------------------
# Helper scripts (weather widget, speed test, unit converter, timer, etc.) need
# these third-party Python libraries. An isolated venv keeps them separate from
# system Python packages managed by pacman to avoid conflicts.
log "Setting up Python venv for scripts..."
python -m venv ~/.config/python-script
~/.config/python-script/bin/pip install speedtest-cli requests pint simpleeval parsedatetime
~/.config/python-script/bin/pip install \
speedtest-cli `# Internet speed test (EWW bar widget)` \
requests `# HTTP client for API-driven widgets` \
pint `# Unit conversion library` \
simpleeval `# Safe maths expression evaluator (wofi-calc)` \
parsedatetime `# Natural-language date/time parser for timer scripts`
# ---------------------------------------------------------------------------
# 13. Udiskie icon fix
# ---------------------------------------------------------------------------
# udiskie expects freedesktop icon names (udiskie-checkbox-checked, etc.) that
# Papirus-Dark does not ship under those exact names. We symlink Papirus-Dark's
# existing checkbox SVGs into the hicolor fallback theme under the names udiskie
# expects, then rebuild the cache so GTK picks them up immediately.
log "Applying Udiskie icon fix..."
PAPIRUS_DIR="/usr/share/icons/Papirus-Dark/status"
HICOLOR_DIR="/usr/share/icons/hicolor/scalable/status"
if [ -d "$PAPIRUS_DIR" ]; then
sudo ln -sf "$PAPIRUS_DIR/checkbox-checked.svg" "$HICOLOR_DIR/udiskie-checkbox-checked.svg"
sudo ln -sf "$PAPIRUS_DIR/checkbox-unchecked.svg" "$HICOLOR_DIR/udiskie-checkbox-unchecked.svg"
# Rebuild the GTK icon cache so apps find the new symlinks without restart.
sudo gtk-update-icon-cache -f -t /usr/share/icons/hicolor
else
warn "Papirus-Dark not found — skipping udiskie icon fix."
fi
# ---------------------------------------------------------------------------
# 14. Enable udiskie
# ---------------------------------------------------------------------------
# udiskie auto-mounts removable drives and shows a tray icon for each volume.
# Enable the systemd unit for future boots and start it immediately so USB
# devices inserted during this session are handled right away.
log "Enabling udiskie service..."
sudo systemctl enable udiskie.service
sudo systemctl start udiskie.service
# ---------------------------------------------------------------------------
# 15. Install config updater and theme script
# ---------------------------------------------------------------------------
# config-updater selectively re-syncs config files from the Dotfiles repo
# without re-running the full installer. updater.conf lists which files are
# managed; update-configs.sh performs the sync.
#
# Symlinks are used (not copies) so that pulling the latest Dotfiles with
# 'git pull' takes effect immediately on the next sync run.
log "Installing config updater and theme script..."
mkdir -p ~/.config/config-updater
ln -sf ~/Dotfiles/desktopenvs/hyprlua/config-updater/updater.conf ~/.config/config-updater/updater.conf
ln -sf ~/Dotfiles/desktopenvs/hyprlua/config-updater/update-configs.sh ~/update-configs.sh
# apply-theme.sh applies the cyberqueer palette across all running apps
# (reloads dunst, refreshes EWW variables, sets GTK/Qt themes).
# Copied rather than symlinked so it is available even when ~/Dotfiles is absent.
cp ~/Dotfiles/apply-theme.sh ~/apply-theme.sh
chmod +x ~/apply-theme.sh

View File

@ -1,7 +1,19 @@
#!/bin/bash
# set -euo pipefail: abort on errors, unset vars, and pipeline failures.
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/../lib/logging.sh"
# plasma-meta: meta-package that pulls in the full Plasma desktop environment.
# sddm: Simple Desktop Display Manager (KDE's preferred login screen).
# sddm-kcm: KCM (KDE Control Module) for configuring SDDM inside System Settings.
# xdg-desktop-portal-kde: portal backend for Flatpak/Wayland screen-share,
# file-chooser, and print dialogs inside the KDE environment.
# konsole dolphin kate gwenview ark spectacle okular elisa: core KDE apps
# (terminal, file manager, editor, image viewer, archiver, screenshots, PDF, music).
# plasma-browser-integration: browser extension that exposes tabs to KDE.
# bluedevil: KDE's Bluetooth manager GUI (sits on top of bluez).
# power-profiles-daemon: exposes power-profile switching (balanced/performance/powersave)
# to KDE's Power Management settings.
log "Installing KDE Plasma desktop..."
sudo pacman -S --noconfirm --needed \
plasma-meta \
@ -16,6 +28,7 @@ sudo pacman -S --noconfirm --needed \
flatpak
log "Enabling services..."
# sddm provides the graphical login screen; must be enabled before rebooting.
sudo systemctl enable sddm.service
sudo systemctl enable NetworkManager.service
sudo systemctl enable bluetooth.service

View File

@ -1,25 +1,166 @@
#!/bin/bash
# ╔══════════════════════════════════════════════════════════════════════════════╗
# ║ setup/modules/core-packages.sh — Core system package installation ║
# ║ ║
# ║ PURPOSE: ║
# ║ Installs all base-layer packages that every configuration of this ║
# ║ system relies on. This is the largest and most time-consuming module. ║
# ║ ║
# ║ WHEN TO RUN: ║
# ║ After package-managers.sh (yay must be present for the AUR packages). ║
# ║ Called "core" in the TUI installer's component checklist. ║
# ║ ║
# ║ CATEGORIES INSTALLED: ║
# ║ - Archiving & file utilities (7zip, atool, dust, fd, fzf, ripgrep) ║
# ║ - Base build tools (base-devel, gcc, rust, rustup, python, ruby) ║
# ║ - Networking (networkmanager, iwd, openssh, wpa_supplicant, bind, ldns) ║
# ║ - Audio (pipewire, wireplumber, libpulse) ║
# ║ - Boot & storage (grub, btrfs-progs, dosfstools, udisks2, udiskie) ║
# ║ - Terminal utilities (btop, htop, tmux, mc, yazi, nano, vim, neovim) ║
# ║ - System monitoring (fastfetch, lshw, lsof, smartmontools, vnstat) ║
# ║ - Security (fail2ban, ufw, pciutils, usbutils) ║
# ║ - Misc: flatpak, distrobox, qrencode, fdupes, lynx, tldr, tree ║
# ╚══════════════════════════════════════════════════════════════════════════════╝
set -euo pipefail
# -e: exit immediately if any package install fails
# -u: error on unset variables
# -o pipefail: catch failures inside pipes
# Load shared logging helpers (log, skip, warn, err functions)
source "$(dirname "${BASH_SOURCE[0]}")/lib/logging.sh"
log "Installing core packages..."
sudo pacman -Syu --noconfirm --needed \
7zip arch-install-scripts atftp atool \
base base-devel bc bind bluez btrfs-progs btop \
cronie curl \
dmidecode distrobox dosfstools dust \
e2fsprogs fail2ban fastfetch fd fdupes ffmpeg firefox flatpak fzf \
gcc git glib2 greetd-tuigreet grub \
hdparm htop inetutils iputils iwd jq ldns less libpulse linux linux-firmware lshw lsof \
lynx \
man-db mc nano neovim networkmanager \
onefetch openbsd-netcat openssh parted pciutils pipewire \
python python-pip qrencode ripgrep rsync ruby-pkg-config rust rustup \
smartmontools strace symlinks sysstat tldr tmux tree \
udisks2 udisks2-btrfs udiskie ufw usbutils \
vim vnstat wget whois wireplumber wireless_tools wpa_supplicant wprs \
yazi zip unzip zram-generator
# Single pacman invocation for efficiency:
# -S: sync (install)
# -yu: update the package database and upgrade existing packages first
# --noconfirm: don't prompt for confirmation (needed for scripted install)
# --needed: skip packages that are already up-to-date (idempotency)
#
# Package groupings with rationale:
sudo pacman -Syu --noconfirm --needed \
# ── Archiving & file utilities ────────────────────────────────────────────
7zip \ # Modern replacement for p7zip — handles .7z, .zip, .rar, etc.
arch-install-scripts \ # genfstab and other Arch install helpers (useful even post-install)
atftp \ # TFTP client/server — used by some network boot tools
atool \ # Universal archive wrapper (handles zip/tar/gz/xz with one command)
\
# ── Base system & build tools ─────────────────────────────────────────────
base \ # Minimal Arch base (filesystem, bash, glibc, etc.)
base-devel \ # AUR build requirements: make, gcc, binutils, pkg-config, etc.
bc \ # Arbitrary precision calculator — used in many shell scripts
bind \ # DNS tools: dig, nslookup, host
bluez \ # Bluetooth protocol stack — enables BT devices
btrfs-progs \ # btrfs filesystem tools: mkfs.btrfs, btrfsck, snapper
btop \ # Interactive resource monitor (CPU/mem/net/disk)
\
cronie \ # Cron daemon for scheduled tasks (enabled later in core.sh)
curl \ # HTTP/FTP transfer tool — used by many installers
\
# ── Hardware & disk utilities ─────────────────────────────────────────────
dmidecode \ # Reads hardware info from DMI/SMBIOS (CPU, memory, BIOS)
distrobox \ # Run other distro containers inside Arch (uses podman/docker)
dosfstools \ # mkfs.fat for FAT/EFI partitions; used when setting up boot
dust \ # Disk usage analyzer — like `du` but cleaner output
\
# ── Filesystem & recovery ────────────────────────────────────────────────
e2fsprogs \ # ext4 filesystem tools: fsck.ext4, mkfs.ext4, tune2fs
fail2ban \ # Bans IPs after repeated auth failures (SSH protection)
fastfetch \ # System info display (neofetch replacement, faster)
fd \ # Fast alternative to `find`, respects .gitignore
fdupes \ # Find duplicate files by checksum
ffmpeg \ # Video/audio processing framework; required by many tools
firefox \ # Default browser (also in DE install, but needed as base)
flatpak \ # Universal app sandbox format; Flathub remote added in pkg-mgr
fzf \ # Fuzzy finder — used by shell, yazi, neovim integrations
\
# ── Compilers & language runtimes ────────────────────────────────────────
gcc \ # GNU C compiler — needed for native AUR builds
git \ # Version control — required everywhere
glib2 \ # GNOME low-level lib; used by many GTK/D-Bus tools
greetd-tuigreet \ # Text-mode login greeter (configured in core.sh)
grub \ # GRUB2 bootloader for x86_64-efi
\
# ── Hardware identification & networking ──────────────────────────────────
hdparm \ # Disk performance testing and low-level ATA control
htop \ # Interactive process viewer (classic alternative to btop)
inetutils \ # hostname, telnet, rsh, ftp utilities
iputils \ # ping, tracepath, arping — basic IP network tools
iwd \ # Intel Wireless Daemon — WiFi backend for NetworkManager
jq \ # JSON processor — used by installer scripts and modules
ldns \ # DNS library + drill tool (alternative to dig)
less \ # Pager for viewing long output; also needed by man
libpulse \ # PulseAudio client library — compatibility shim for pipewire
linux \ # The Linux kernel itself
linux-firmware \ # Microcode and firmware blobs for hardware support
lshw \ # Detailed hardware listing (CPU, memory, PCI devices)
lsof \ # List open files — invaluable for debugging processes/sockets
\
lynx \ # Text-mode web browser — useful in TTY-only environments
\
# ── Editors & documentation ──────────────────────────────────────────────
man-db \ # Man page database and viewer
mc \ # Midnight Commander — two-pane terminal file manager
nano \ # Simple terminal text editor for quick edits
neovim \ # Primary editor (configured via dotfiles/nvim/)
networkmanager \ # Network connection manager daemon
\
# ── Fetch & monitoring ────────────────────────────────────────────────────
onefetch \ # Git repo summary in the terminal (like neofetch for repos)
openbsd-netcat \ # Netcat implementation — port scanning, simple TCP connections
openssh \ # SSH client and server (sshd enabled in optional module)
parted \ # Disk partitioning tool — used by arch-autoinstall.sh
pciutils \ # lspci — list PCI devices (needed for GPU detection)
pipewire \ # Modern audio/video server (replaces PulseAudio + JACK)
\
# ── Programming languages ────────────────────────────────────────────────
python \ # Python 3 interpreter
python-pip \ # Python package manager
qrencode \ # Generate QR codes in terminal — useful for sharing WiFi/URLs
ripgrep \ # Fast recursive grep replacement (rg command)
rsync \ # Efficient file sync over SSH or locally
ruby-pkg-config \ # Ruby gem build helper — needed by some AUR packages
rust \ # Rust compiler (also installed via rustup for toolchain mgmt)
rustup \ # Rust toolchain manager — switches stable/nightly/beta
\
# ── System monitoring & debugging ────────────────────────────────────────
smartmontools \ # S.M.A.R.T. disk health monitoring (smartctl)
strace \ # System call tracer — debugging tool for process behavior
symlinks \ # Scans and optionally fixes broken symlinks
sysstat \ # System performance stats: iostat, mpstat, pidstat, sar
tldr \ # Simplified man pages with practical examples
tmux \ # Terminal multiplexer — persistent sessions over SSH
tree \ # Display directory structure as a tree
\
# ── Disk & USB management ────────────────────────────────────────────────
udisks2 \ # D-Bus service for auto-mounting removable media
udisks2-btrfs \ # btrfs extension for udisks2
udiskie \ # User-space udisks2 automounter daemon
ufw \ # Uncomplicated Firewall — iptables frontend
usbutils \ # lsusb — list USB devices
\
# ── Network tools & misc ─────────────────────────────────────────────────
vim \ # Classic Vi IMproved editor (backup editor, always present)
vnstat \ # Network traffic monitor — tracks usage per interface
wget \ # HTTP/FTP file downloader
whois \ # Domain name lookup tool
wireplumber \ # Session/policy manager for PipeWire (required for audio)
wireless_tools \ # Legacy iwconfig, iwlist — sometimes needed for troubleshooting
wpa_supplicant \ # WPA2/WPA3 supplicant for WiFi authentication
wprs \ # Wayland proxy for running X11 apps in Wayland sessions
\
# ── File manager & archive ────────────────────────────────────────────────
yazi \ # Terminal file manager with image preview and fuzzy search
zip \ # Create ZIP archives
unzip \ # Extract ZIP archives
zram-generator # Systemd-based zram compressed swap setup (improves RAM efficiency)
# ── AUR packages ──────────────────────────────────────────────────────────────
# Only install packages that are NOT available in official repos.
# pamtester: PAM module tester — used to verify FIDO2/U2F PAM stack without logging out.
# Available only in AUR because it's a small utility not deemed mainline-worthy.
log "Installing AUR packages..."
yay -S --aur --noconfirm --needed pamtester

View File

@ -1,20 +1,68 @@
#!/bin/bash
# ╔══════════════════════════════════════════════════════════════════════════════╗
# ║ setup/modules/core.sh — Core system services enablement ║
# ║ ║
# ║ PURPOSE: ║
# ║ Enables the essential systemd services that every installation needs. ║
# ║ Also deploys the greetd login manager config from the dotfiles repo. ║
# ║ ║
# ║ WHEN TO RUN: ║
# ║ After core-packages.sh — the services must be installed before they can ║
# ║ be enabled. Called "svc" in the TUI installer's component checklist. ║
# ║ ║
# ║ SERVICES ENABLED: ║
# ║ - NetworkManager: manages wired/wireless network connections ║
# ║ - cronie: cron daemon for scheduled tasks ║
# ║ - greetd: minimal display/login manager (uses tuigreet TUI) ║
# ║ - fail2ban: bans IPs after repeated failed SSH/auth attempts ║
# ║ - udisks2: auto-mounts USB drives and other removable media ║
# ╚══════════════════════════════════════════════════════════════════════════════╝
set -euo pipefail
# -e: exit immediately on any error (service enable failures abort the script)
# -u: treat unset variables as errors
# -o pipefail: fail if any pipe stage fails
# Load shared logging helpers (log, skip, warn, err functions)
source "$(dirname "${BASH_SOURCE[0]}")/lib/logging.sh"
# ── NetworkManager ─────────────────────────────────────────────────────────────
# WHY: Arch ships with no network daemon enabled by default. NetworkManager is
# the most user-friendly option — handles DHCP, WiFi, VPN, and has applets.
# NOTE: systemctl enable only marks it to start at boot; it doesn't start it now.
log "Enabling NetworkManager..."
sudo systemctl enable NetworkManager.service
# ── cronie ────────────────────────────────────────────────────────────────────
# WHY: Provides the system cron daemon. Some tools (backups, vnstat stats,
# fail2ban cleanup) rely on cron jobs. Arch does not enable it by default.
log "Enabling cronie..."
sudo systemctl enable cronie.service
# ── greetd / tuigreet ─────────────────────────────────────────────────────────
# WHY: greetd is a minimal, standards-compliant display manager (login screen).
# We use the tuigreet greeter which is a TUI (text-mode, no GPU needed).
# It's lighter than GDM/SDDM and works well on TTY in Wayland setups.
#
# HOW: Copy our pre-configured config.toml from the dotfiles repo into /etc/greetd/
# then enable the service. The config.toml specifies which greeter to run
# and optionally auto-login settings.
# -f flag forces overwrite of any existing config.
log "Deploying greetd config..."
sudo cp -f ~/Dotfiles/desktopenvs/hyprland/greetd-tuigreet/config.toml /etc/greetd/config.toml
sudo systemctl enable greetd.service
# ── fail2ban ──────────────────────────────────────────────────────────────────
# WHY: Protects against brute-force attacks by monitoring log files and
# temporarily banning IPs that show malicious signs (too many failed logins).
# Important on any machine with SSH open to the network.
log "Enabling fail2ban..."
sudo systemctl enable fail2ban.service
# ── udisks2 ───────────────────────────────────────────────────────────────────
# WHY: udisks2 provides automatic mounting of USB drives and other removable
# media via D-Bus. Required by file managers (Thunar, pcmanfm) and desktop
# utilities that want to offer "Open when inserted" functionality.
log "Enabling udisks2..."
sudo systemctl enable udisks2.service

View File

@ -1,39 +1,104 @@
#!/bin/bash
# ╔══════════════════════════════════════════════════════════════════════════════╗
# ║ setup/modules/lib/logging.sh — Shared logging helpers ║
# ║ ║
# ║ PURPOSE: ║
# ║ Provides colored console output functions used by every module script. ║
# ║ Source this file at the top of any module with: ║
# ║ source "$(dirname "${BASH_SOURCE[0]}")/lib/logging.sh" ║
# ║ ║
# ║ WHY A SHARED LIB: ║
# ║ Centralizes formatting so all modules have consistent output. One change ║
# ║ here affects every module's output style. ║
# ║ ║
# ║ ALSO PROVIDES: ║
# ║ - ensure_flatpak: bootstrap Flatpak + Flathub for any app module ║
# ║ - apply_flatpak_theme: apply the cyberqueer GTK theme to a Flatpak app ║
# ╚══════════════════════════════════════════════════════════════════════════════╝
# Shared logging helpers — source this in every module
# ── ANSI color codes ──────────────────────────────────────────────────────────
# These are escape sequences that terminal emulators interpret as colors.
# Format: \e[<code>m where code 31=red, 32=green, 33=yellow, 0=reset.
GREEN="\e[32m"
YELLOW="\e[33m"
RED="\e[31m"
RESET="\e[0m"
# ── Logging functions ──────────────────────────────────────────────────────────
# Each function takes a message string and formats it with a colored prefix.
# WHY: Consistent, colored output makes it easy to scan install logs at a glance
# and immediately identify success, skips, warnings, or errors.
# log: normal progress message — green [+] prefix
# Used for: "doing X now", successful completions
log() { printf "${GREEN}[+] %s${RESET}\n" "$*"; }
# skip: indicates something was already done — yellow [~] prefix
# Used for: idempotency checks ("X already installed")
skip() { printf "${YELLOW}[~] %s${RESET}\n" "$*"; }
# warn: non-fatal warning — yellow [!] prefix to stderr
# Used for: recoverable issues that don't abort the module
warn() { printf "${YELLOW}[!] %s${RESET}\n" "$*" >&2; }
# err: fatal error — red [✖] prefix to stderr
# Used for: unrecoverable failures (caller decides whether to exit)
err() { printf "${RED}[✖] %s${RESET}\n" "$*" >&2; }
# ── ensure_flatpak ─────────────────────────────────────────────────────────────
# WHY: Many optional app modules install via Flatpak. Rather than duplicating
# the bootstrap code in every app script, this function handles it once.
# WHAT: Installs flatpak if missing, adds Flathub remote if not already present.
ensure_flatpak() {
# Check if flatpak binary is available; install it via pacman if not
if ! command -v flatpak &>/dev/null; then
log "Installing flatpak..."
sudo pacman -S --noconfirm --needed flatpak
fi
# Check if the flathub remote is already configured to avoid duplicate remotes.
# flatpak remotes lists configured remotes; grep filters for 'flathub'.
if ! flatpak remotes 2>/dev/null | grep -q flathub; then
log "Adding Flathub remote..."
# --if-not-exists is redundant given the check above, but adds safety
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
fi
}
# ── apply_flatpak_theme ────────────────────────────────────────────────────────
# WHY: Flatpak apps run in sandboxes and can't access the host ~/.themes directory
# by default. We must explicitly grant filesystem access AND set GTK_THEME.
# WHAT: Copies the cyberqueer GTK theme into ~/.themes, grants the Flatpak app
# read-only access to that directory, and overrides the GTK_THEME env var.
#
# Usage: apply_flatpak_theme <app-id>
# e.g. apply_flatpak_theme org.gnome.gedit
apply_flatpak_theme() {
local app_id="$1"
local theme_name="cyberqueer"
# The theme lives in the dotfiles repo under gtk-themes/cyberqueer/
local theme_src="$HOME/Dotfiles/gtk-themes/$theme_name"
local themes_dir="$HOME/.themes"
# Guard: if the theme source doesn't exist, skip gracefully with a warning
if [[ ! -d "$theme_src" ]]; then
warn "Cyberqueer theme not found at $theme_src — skipping Flatpak theme override."
return 0
fi
# Copy the theme into the user's ~/.themes/ so it's accessible
mkdir -p "$themes_dir"
cp -r "$theme_src" "$themes_dir/$theme_name"
# Grant the Flatpak app read-only access to ~/.themes using a per-user override.
# This avoids needing a global system-wide Flatpak override.
flatpak override --user --filesystem="$themes_dir":ro "$app_id"
# Set GTK_THEME env var for this app so GTK picks up the correct theme name
flatpak override --user --env=GTK_THEME="$theme_name" "$app_id"
log "Cyberqueer theme applied to $app_id."
}

View File

@ -1,15 +1,39 @@
#!/bin/bash
# ============================================================
# anti-malware.sh — Anti-malware and rootkit detection stack
# ============================================================
# Installs ClamAV (antivirus), ClamTk (GUI front-end), rkhunter
# (rootkit hunter), and chkrootkit (AUR rootkit scanner) so the
# system has layered malware-detection coverage. These tools are
# optional because most home Arch desktops don't need a resident
# scanner, but they are valuable on machines that handle
# untrusted files or shared network storage.
# ============================================================
set -euo pipefail
# Load shared logging helpers (log, skip, warn) from the dotfiles lib
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
# ── Core packages via pacman ──────────────────────────────────────────────────
# clamav: the open-source antivirus engine and CLI scanner (clamscan, clamdscan)
# clamtk: optional GTK GUI for ClamAV, useful for one-off scans without the CLI
# rkhunter: scans for known rootkits, backdoors, and suspicious local changes
log "Installing anti-malware tools (clamav, clamtk, rkhunter)..."
sudo pacman -S --noconfirm --needed \
clamav clamtk rkhunter
# ── chkrootkit (AUR) ─────────────────────────────────────────────────────────
# chkrootkit: a second rootkit scanner; complements rkhunter with different
# detection heuristics. Available from AUR only (not in the official repos).
log "Installing chkrootkit (AUR)..."
yay -S --aur --noconfirm --needed chkrootkit
# initialise ClamAV database (first run)
# ── Initial ClamAV virus database ────────────────────────────────────────────
# freshclam downloads the official ClamAV virus-definition database.
# We only run it when the main database file is absent to avoid a redundant
# download on repeat runs of the installer.
# main.cvd = compressed virus database (fresh download)
# main.cld = incremental update (already exists if previously initialised)
if [[ ! -f /var/lib/clamav/main.cvd ]] && [[ ! -f /var/lib/clamav/main.cld ]]; then
log "Running initial freshclam (virus database update)..."
sudo freshclam
@ -17,14 +41,21 @@ else
skip "ClamAV database already present."
fi
# cron job: update virus definitions twice a day
# ── Cron job for automatic virus-definition updates ──────────────────────────
# ClamAV definitions become stale quickly; running freshclam twice daily keeps
# the scanner effective. We write a system-wide cron snippet to /etc/cron.d/
# rather than a user crontab so the update runs even when no user is logged in.
# The quiet flag suppresses normal output; stderr is discarded so cron mail is
# not generated on success.
CRON_FILE=/etc/cron.d/freshclam
if [[ ! -f "$CRON_FILE" ]]; then
log "Installing freshclam cron job (twice daily)..."
# tee writes to the privileged path without running the whole script as root
sudo tee "$CRON_FILE" > /dev/null <<'EOF'
# Update ClamAV virus definitions twice a day
0 */12 * * * root /usr/bin/freshclam --quiet 2>/dev/null
EOF
# 644 = readable by all (cron needs to read it), writable only by root
sudo chmod 644 "$CRON_FILE"
else
skip "freshclam cron job already configured."

View File

@ -1,9 +1,35 @@
#!/bin/bash
# ============================================================
# ardour.sh — Ardour digital audio workstation (DAW)
# ============================================================
# Installs Ardour, a professional-grade open-source DAW for
# recording, editing, and mixing multi-track audio and MIDI.
# It is distributed as a Flatpak here to get the upstream-
# sanctioned build with all codec/plugin support enabled, and
# to isolate it from system audio libraries that may differ
# across Arch versions. It is an optional module because most
# users do not need a full DAW; lighter options like Audacity
# or LMMS are separate modules.
# ============================================================
set -euo pipefail
# Load shared logging helpers and the ensure_flatpak / apply_flatpak_theme
# functions from the dotfiles lib
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Ardour (Flatpak)..."
# ensure_flatpak: verifies that flatpak and the Flathub remote are set up;
# defined in lib/logging.sh (or a sourced helper). Idempotent.
ensure_flatpak
# Install Ardour from the Flathub repository.
# -y skips the interactive confirmation prompt so the script is non-interactive.
flatpak install -y flathub org.ardour.Ardour
# apply_flatpak_theme: applies the cyberqueer GTK theme override so Ardour's
# GTK widgets match the rest of the desktop, rather than using the Flatpak
# default Adwaita theme.
apply_flatpak_theme "org.ardour.Ardour"
log "Ardour installed."

View File

@ -1,9 +1,31 @@
#!/bin/bash
# ============================================================
# audacity.sh — Audacity audio editor
# ============================================================
# Installs Audacity, a cross-platform, open-source audio editor
# and recorder. It is useful for quick edits, noise removal,
# format conversion, and podcast production — lighter-weight
# than a full DAW like Ardour. Flatpak is preferred over the
# AUR package because it bundles all FFmpeg/codec dependencies
# and avoids conflicts with system libraries.
# ============================================================
set -euo pipefail
# Load shared logging helpers and the ensure_flatpak / apply_flatpak_theme
# functions from the dotfiles lib
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Audacity (Flatpak)..."
# ensure_flatpak: verifies that flatpak and the Flathub remote are present;
# idempotent — safe to call even if Flatpak is already configured.
ensure_flatpak
# Install from Flathub. -y suppresses the interactive confirmation.
flatpak install -y flathub org.audacityteam.Audacity
# apply_flatpak_theme: injects the cyberqueer GTK theme into Audacity's
# Flatpak environment so it renders consistently with the rest of the desktop.
apply_flatpak_theme "org.audacityteam.Audacity"
log "Audacity installed."

View File

@ -1,12 +1,45 @@
#!/bin/bash
# ============================================================
# blender-povray.sh — Blender 3D suite + POV-Ray ray-tracer
# ============================================================
# Installs Blender (3D modelling, animation, compositing) via
# Flatpak and POV-Ray (a classic CPU ray-tracer with its own
# scene-description language) via pacman.
#
# Blender is delivered as a Flatpak to get the upstream official
# build with all drivers and Python scripting enabled.
# POV-Ray comes from the official Arch repos because it is a
# small, self-contained CLI tool with no sandboxing concerns.
#
# These are grouped together because POV-Ray integrates with
# Blender via the Blender2POVray addon, allowing Blender scenes
# to be rendered with POV-Ray's photorealistic ray-tracing.
# ============================================================
set -euo pipefail
# Load shared logging helpers and Flatpak utility functions
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Blender (Flatpak)..."
# ensure_flatpak: checks that the flatpak binary and the Flathub remote exist;
# idempotent — no-op if already set up.
ensure_flatpak
# Install Blender from Flathub. The Flatpak edition ships its own Python,
# CUDA/HIP compute libraries, and codec support pre-bundled, avoiding
# dependency conflicts with the system Python or GPU driver packages.
flatpak install -y flathub org.blender.Blender
# apply_flatpak_theme: sets the cyberqueer GTK theme for Blender's dialogs
# (the main 3D viewport uses its own renderer, but file choosers and system
# dialogs benefit from a consistent theme).
apply_flatpak_theme "org.blender.Blender"
# ── POV-Ray via pacman ────────────────────────────────────────────────────────
# povray: the Persistence Of Vision Raytracer, a mature scene-description-
# language-based ray-tracer. Installed system-wide so the `povray` CLI
# is available to Blender's POV-Ray export add-on and from the terminal.
log "Installing POV-Ray (pacman)..."
sudo pacman -S --noconfirm --needed povray

View File

@ -1,10 +1,37 @@
#!/bin/bash
# ============================================================
# butter.sh — Butter Btrfs snapshot GUI
# ============================================================
# Installs Butter, a graphical front-end for managing Btrfs
# snapshots (create, browse, restore, schedule). It provides
# a user-friendly alternative to running btrfs subvolume
# commands by hand.
#
# btrfs-progs is the essential userspace toolkit needed by
# every tool that manages Btrfs filesystems; it is installed
# from the official repos first because it is a hard dependency.
# Butter itself is AUR-only.
#
# This is an optional module because it is only useful on
# systems whose root (or data) filesystem is Btrfs.
# ============================================================
set -euo pipefail
# Load shared logging helpers from the dotfiles lib
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
# ── btrfs-progs ───────────────────────────────────────────────────────────────
# btrfs-progs: the official Btrfs userspace tools (mkfs.btrfs, btrfs subvolume,
# btrfs send/receive, etc.). Required at runtime by Butter and useful on its
# own for CLI snapshot management.
log "Installing btrfs-progs..."
sudo pacman -S --noconfirm --needed btrfs-progs
# ── Butter (AUR) ─────────────────────────────────────────────────────────────
# --answerdiff None : skip the "show diff?" prompt during AUR builds
# --answerclean All : answer "clean" to the "clean build dir?" prompt
# --noconfirm : suppress all remaining yay/makepkg confirmation prompts
log "Installing butter (AUR)..."
yay -S --answerdiff None --answerclean All --noconfirm butter
log "butter installed."

View File

@ -1,23 +1,66 @@
#!/bin/bash
# ============================================================
# caldav-sync.sh — CalDAV calendar sync stack
# ============================================================
# Sets up a full offline-capable CalDAV synchronisation stack:
#
# vdirsyncer — syncs CalDAV server ↔ local ICS files
# khal — CLI/TUI calendar viewer and editor
# python-icalendar — Python library for parsing .ics files
# ics-to-calendarim — custom script that converts ICS files
# into the JSON cache format expected by
# the calendar.vim Neovim plugin
#
# A systemd user timer fires every 15 minutes to keep local
# calendars current without any manual intervention.
#
# This is an optional module because CalDAV sync is only useful
# if the user has a remote calendar server (Nextcloud, Radicale,
# Google Calendar export, etc.).
# ============================================================
set -euo pipefail
# Load shared logging helpers from the dotfiles lib
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
# ── Core packages ─────────────────────────────────────────────────────────────
# vdirsyncer : the CalDAV/CardDAV sync daemon that mirrors remote
# calendars to local Maildir-style .ics files
# khal : a CLI/TUI calendar reader/editor that reads the local
# ICS files and presents them in a human-friendly view
# python-icalendar : Python library needed by the ics-to-calendarim helper
# to parse VEVENT components from .ics files
log "Installing CalDAV sync stack..."
sudo pacman -S --noconfirm --needed vdirsyncer khal python-icalendar
# ── Credentials ───────────────────────────────────────────────────────────────
# Collect the minimum information needed to configure vdirsyncer.
# -r = raw input (no backslash escaping), -p = prompt, -s = silent (password)
read -rp "CalDAV server URL (e.g. https://dav.example.com/cal/): " CALDAV_URL
read -rp "Username : " CALDAV_USER
read -rsp "Password : " CALDAV_PASS; echo
read -rp "Calendar display name [Personal] : " CAL_NAME
# Default to "Personal" if the user pressed Enter without typing a name
CAL_NAME="${CAL_NAME:-Personal}"
# Local directory where vdirsyncer will store the .ics files, one sub-
# directory per calendar collection discovered on the server.
CALDAV_DIR="$HOME/.local/share/calendars"
mkdir -p "$CALDAV_DIR"
# ── vdirsyncer ────────────────────────────────────────────────────────────────
# ── vdirsyncer configuration ──────────────────────────────────────────────────
# vdirsyncer reads a single INI-style config file.
# We create the status directory first (vdirsyncer stores sync state there
# to track what has been pushed/pulled for each item).
log "Writing ~/.config/vdirsyncer/config..."
mkdir -p ~/.config/vdirsyncer ~/.local/share/vdirsyncer/status
# The config defines:
# [general] — global options (where to store sync state)
# [pair calendars] — a named pair that links local ↔ remote storage
# [storage *] — individual storage backends (filesystem + caldav)
# "collections = ["from b"]" means: discover all collections that exist on
# the remote (b) side and create matching local directories automatically.
cat > ~/.config/vdirsyncer/config << EOF
[general]
status_path = "$HOME/.local/share/vdirsyncer/status/"
@ -39,15 +82,24 @@ url = "$CALDAV_URL"
username = "$CALDAV_USER"
password = "$CALDAV_PASS"
EOF
# Restrict permissions: config contains plaintext password
chmod 600 ~/.config/vdirsyncer/config
# "discover" queries the server for all available collections and records them
# in the status directory so future `vdirsyncer sync` commands know what to sync.
# `yes |` pre-answers any interactive "create local directory?" prompts.
# `|| true` prevents the script from aborting if discovery finds nothing new.
log "Discovering CalDAV collections (confirm any prompts with y)..."
yes | vdirsyncer discover calendars || true
# Pull all events from the server for the first time.
log "Running initial sync..."
vdirsyncer sync
# ── khal (CLI calendar companion) ────────────────────────────────────────────
# ── khal configuration ────────────────────────────────────────────────────────
# khal needs one [[calendar]] section per local ICS directory.
# We use an inline Python script so we can iterate over discovered directories
# dynamically instead of hard-coding calendar names.
log "Writing ~/.config/khal/config (per-calendar entries)..."
mkdir -p ~/.config/khal
python3 - "$CALDAV_DIR" << 'PYEOF'
@ -55,13 +107,18 @@ import sys
from pathlib import Path
cal_root = Path(sys.argv[1])
# Cycle through a set of terminal colours so different calendars are visually
# distinct in khal's output.
colors = ["light blue", "light green", "light red", "light magenta", "light cyan"]
dirs = sorted(d for d in cal_root.iterdir() if d.is_dir())
lines = ["[calendars]"]
for i, d in enumerate(dirs):
# Each sub-directory created by vdirsyncer is one calendar collection.
lines += [f" [[{d.name}]]", f" path = {d}", f" color = {colors[i % len(colors)]}"]
# [sqlite] — khal caches parsed events here for faster startup
# [locale] — display preferences for times and dates
lines += [
"", "[sqlite]", "path = ~/.local/share/khal/khal.db",
"", "[locale]", "timeformat = %H:%M", "dateformat = %Y-%m-%d",
@ -72,6 +129,10 @@ lines += [
PYEOF
# ── ICS → calendar.vim cache converter ───────────────────────────────────────
# calendar.vim (a Neovim plugin) expects event data in a specific JSON cache
# format at ~/.cache/calendar.vim/local/. This helper script reads the local
# ICS files maintained by vdirsyncer and writes that JSON cache so calendar.vim
# can show events without hitting the network.
log "Installing ics-to-calendarim converter..."
mkdir -p ~/.local/bin
cat > ~/.local/bin/ics-to-calendarim << 'PYEOF'
@ -95,10 +156,19 @@ CALDAV_DIR = Path.home() / ".local/share/calendars"
CACHE_DIR = Path.home() / ".cache/calendar.vim/local"
KHAL_CFG = Path.home() / ".config/khal/config"
# Hex colours for calendar.vim's event display (one per calendar, cycled)
COLORS = ["#4CAF50", "#2196F3", "#FF5722", "#9C27B0", "#FF9800"]
# Matching terminal colour names for khal (kept in sync so both tools agree)
KHAL_COLORS = ["light blue", "light green", "light red", "light magenta", "light cyan"]
def to_naive(dt):
"""Normalise a DTSTART/DTEND value to a naive string + metadata.
Returns (string_repr, is_timed, year, month).
Timezone-aware datetimes are converted to UTC before stripping tzinfo
so that comparisons are consistent across DST transitions.
All-day dates return an ISO date string (YYYY-MM-DD) with is_timed=False.
"""
if isinstance(dt, datetime):
if dt.tzinfo is not None:
dt = dt.astimezone(timezone.utc).replace(tzinfo=None)
@ -109,10 +179,12 @@ def to_naive(dt):
def main():
CACHE_DIR.mkdir(parents=True, exist_ok=True)
cal_items = []
cal_items = [] # list of calendar metadata objects for calendarList
# Iterate over each calendar collection directory (one per CalDAV calendar)
for idx, cal_dir in enumerate(sorted(d for d in CALDAV_DIR.iterdir() if d.is_dir())):
cal_id = cal_dir.name
# Human-readable name: replace underscores with spaces and title-case
cal_summary = cal_dir.name.replace("_", " ").title()
cal_items.append({
"id": cal_id,
@ -121,15 +193,17 @@ def main():
"foregroundColor": "#ffffff",
})
by_month: dict = {}
by_month: dict = {} # keyed by (year, month) → list of event objects
for ics in cal_dir.glob("*.ics"):
try:
cal = iCal.from_ical(ics.read_bytes())
except Exception as e:
print(f"warning: skipping {ics.name}: {e}", file=sys.stderr)
continue
# Walk all VEVENT components in the ICS file
for comp in cal.walk("VEVENT"):
dtstart = comp.get("DTSTART")
# If DTEND is missing, fall back to DTSTART (zero-duration event)
dtend = comp.get("DTEND") or comp.get("DTSTART")
if not dtstart:
continue
@ -140,21 +214,28 @@ def main():
uid = str(comp.get("UID", ics.stem))
summary = str(comp.get("SUMMARY", ""))
key = (yr, mo)
# Group events by month so we write one JSON file per month
by_month.setdefault(key, []).append({
"id": uid,
"summary": summary,
# calendar.vim distinguishes timed events ("dateTime") from
# all-day events ("date") by which key is present
"start": {"dateTime": s_str} if is_timed else {"date": s_str},
"end": {"dateTime": e_str} if is_timed else {"date": e_str},
})
# Write one JSON file per (calendar, year, month) combination.
# Path structure mirrors what calendar.vim expects for its local cache.
for (yr, mo), events in by_month.items():
out = CACHE_DIR / cal_id / f"{yr:04d}" / f"{mo:02d}"
out.mkdir(parents=True, exist_ok=True)
(out / "0").write_text(json.dumps({"items": events}))
# Write the top-level calendar list so calendar.vim knows which calendars exist
(CACHE_DIR / "calendarList").write_text(json.dumps({"items": cal_items}))
# regenerate khal config so new calendars are picked up automatically
# Also regenerate the khal config so any newly discovered calendars are
# automatically added without requiring a manual config edit.
cal_dirs = sorted(d for d in CALDAV_DIR.iterdir() if d.is_dir())
khal_lines = ["[calendars]"]
for i, d in enumerate(cal_dirs):
@ -170,15 +251,25 @@ def main():
if __name__ == "__main__":
main()
PYEOF
# Make the helper executable so it can be called directly from the PATH
chmod +x ~/.local/bin/ics-to-calendarim
# Build the initial calendar.vim cache from the events we just synced
log "Running initial calendar.vim cache build..."
~/.local/bin/ics-to-calendarim
# ── systemd user timer ────────────────────────────────────────────────────────
# A systemd user timer keeps calendars current automatically.
# Using systemd (instead of cron) lets the unit run without requiring root and
# integrates cleanly with the user's login session.
log "Installing vdirsyncer systemd user timer (every 15 min)..."
mkdir -p ~/.config/systemd/user
# The service unit runs vdirsyncer sync then rebuilds the calendar.vim cache.
# Type=oneshot: the service is considered active only while the process runs;
# it exits when done rather than staying in the background.
# After=network-online.target: ensures we don't try to sync before the network
# is available (important at boot).
cat > ~/.config/systemd/user/vdirsyncer.service << EOF
[Unit]
Description=Sync CalDAV and rebuild calendar.vim cache
@ -190,6 +281,9 @@ ExecStart=/usr/bin/vdirsyncer sync
ExecStartPost=$HOME/.local/bin/ics-to-calendarim
EOF
# The timer unit triggers the service on a schedule.
# OnBootSec=2min : first run 2 minutes after login (let the network settle)
# OnUnitActiveSec=15min : repeat every 15 minutes after the last activation
cat > ~/.config/systemd/user/vdirsyncer.timer << EOF
[Unit]
Description=Run vdirsyncer every 15 minutes
@ -203,7 +297,9 @@ Unit=vdirsyncer.service
WantedBy=timers.target
EOF
# Reload systemd user daemon so it picks up the new unit files
systemctl --user daemon-reload
# Enable and immediately start the timer; "--now" starts it without a reboot
systemctl --user enable --now vdirsyncer.timer
log "CalDAV sync configured. Events will appear in calendar.vim automatically."

View File

@ -1,7 +1,29 @@
#!/bin/bash
# ============================================================
# cecilia.sh — Cecilia audio signal-processing environment
# ============================================================
# Installs Cecilia, a Python/GUI front-end for CSound that
# provides a set of built-in audio modules for sound design,
# granular synthesis, spectral processing, and more. It is
# aimed at composers and audio researchers who want to
# experiment with synthesis and processing without writing
# raw CSound code.
#
# Cecilia is only available through the AUR on Arch Linux.
# This is an optional module because it is a niche tool
# relevant mainly to users involved in electroacoustic music
# or audio research.
# ============================================================
set -euo pipefail
# Load shared logging helpers from the dotfiles lib
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
# ── Cecilia (AUR) ─────────────────────────────────────────────────────────────
# --answerdiff None : skip the "show diff?" prompt during AUR builds
# --answerclean All : answer "clean" to the "clean build dir?" prompt
# --noconfirm : suppress all remaining yay/makepkg confirmation prompts
log "Installing Cecilia (AUR)..."
yay -S --answerdiff None --answerclean All --noconfirm cecilia
log "Cecilia installed."

View File

@ -1,9 +1,33 @@
#!/bin/bash
# ============================================================
# chromium.sh — Chromium browser (Flatpak)
# ============================================================
# Installs the open-source Chromium browser via Flatpak.
# Chromium is the upstream base from which Google Chrome is
# derived. It is useful as a reference browser for web
# development/testing and as a non-Google-branded alternative
# to Chrome.
#
# Flatpak is preferred over the official Arch chromium package
# so that the browser runs in a sandboxed environment with its
# own set of libraries, reducing the attack surface and
# avoiding dependency conflicts with the system.
# ============================================================
set -euo pipefail
# Load shared logging helpers and Flatpak utility functions
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Chromium (Flatpak)..."
# ensure_flatpak: verifies Flatpak and Flathub are set up; idempotent.
ensure_flatpak
# Install Chromium from Flathub. -y skips the confirmation prompt.
flatpak install -y flathub org.chromium.Chromium
# apply_flatpak_theme: applies the cyberqueer GTK theme so Chromium's file
# dialogs and context menus match the rest of the desktop.
apply_flatpak_theme "org.chromium.Chromium"
log "Chromium installed."

View File

@ -1,13 +1,42 @@
#!/bin/bash
# ============================================================
# claude.sh — Claude Code CLI (Anthropic)
# ============================================================
# Installs Claude Code, Anthropic's official AI-powered CLI
# coding assistant, as a global npm package.
#
# Claude Code requires Node.js / npm. The script first checks
# whether npm is already on PATH; if not, it attempts to
# activate nvm (Node Version Manager) from the default location
# ~/.nvm so the correct Node.js version is available before
# running the install.
#
# This is an optional module because Claude Code is only useful
# to developers who want AI-assisted coding inside the terminal,
# and it requires an Anthropic API key to function.
# ============================================================
set -euo pipefail
# Load shared logging helpers from the dotfiles lib
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Claude Code via npm..."
# Check whether npm is already available on PATH.
# If not, try to source nvm so it places the correct npm into PATH.
# This handles the common case where Node.js was installed through nvm
# rather than pacman, meaning it isn't in the system PATH by default.
if ! command -v npm &>/dev/null; then
log "Sourcing nvm to get npm..."
export NVM_DIR="$HOME/.nvm"
# nvm.sh sets up the nvm function and adds the active Node.js to PATH.
# The -s test checks that the file exists and is non-empty before sourcing.
[[ -s "$NVM_DIR/nvm.sh" ]] && source "$NVM_DIR/nvm.sh"
fi
# Install Claude Code globally so the `claude` command is available system-wide.
# -g installs to the global npm prefix (typically ~/.nvm/versions/node/<ver>/lib
# when using nvm, or /usr/local/lib when using system npm).
npm install -g @anthropic-ai/claude-code
log "Claude Code installed."

View File

@ -1,19 +1,60 @@
#!/bin/bash
# ============================================================
# cockpit.sh — Cockpit web-based server management UI
# ============================================================
# Installs Cockpit, a browser-based administration interface
# that provides real-time system monitoring, journal viewing,
# storage management, and more — all accessible at
# https://localhost:9090.
#
# Packages installed:
# cockpit — core web UI and API layer
# cockpit-pcp — Performance Co-Pilot integration
# (enables historical metrics charts)
# pcp — Performance Co-Pilot daemon (required by cockpit-pcp)
# cockpit-machines — virtual machine management via libvirt
# cockpit-podman — container management via Podman API
# cockpit-navigator — file browser add-on for Cockpit
#
# The core and pcp packages come from the official Arch repos;
# the plugin packages (machines, podman, navigator) are
# AUR-only.
#
# This is an optional module because Cockpit is primarily
# useful on servers or development machines; it is not needed
# on a standard desktop.
# ============================================================
set -euo pipefail
# Load shared logging helpers from the dotfiles lib
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
# ── Core Cockpit packages (official repos) ────────────────────────────────────
# cockpit : the main web server component and administration framework
# cockpit-pcp : bridge between Cockpit and PCP; enables the metrics history page
# pcp : Performance Co-Pilot — collects and archives system performance
# data (CPU, memory, disk I/O, etc.) that cockpit-pcp then displays
log "Installing Cockpit (core + official plugins)..."
sudo pacman -S --noconfirm --needed \
cockpit \
cockpit-pcp \
pcp
# ── Cockpit plugin extensions (AUR) ──────────────────────────────────────────
# cockpit-machines : manage QEMU/KVM virtual machines directly from the web UI
# cockpit-podman : manage rootless and rootful Podman containers
# cockpit-navigator : graphical file manager widget inside Cockpit
log "Installing Cockpit AUR plugins (machines, podman, navigator)..."
yay -S --answerdiff None --answerclean All --noconfirm \
cockpit-machines \
cockpit-podman \
cockpit-navigator
# ── Enable Cockpit socket activation ─────────────────────────────────────────
# Cockpit uses systemd socket activation: the cockpit.socket unit listens on
# port 9090 and starts cockpit.service only when an incoming connection arrives.
# This is more efficient than keeping the full web server running at all times.
log "Enabling Cockpit socket..."
sudo systemctl enable cockpit.socket
log "Cockpit enabled. Web UI available at https://localhost:9090"

View File

@ -1,7 +1,28 @@
#!/bin/bash
# ============================================================
# codeblocks.sh — Code::Blocks IDE
# ============================================================
# Installs Code::Blocks, a free, open-source, cross-platform
# C/C++ and Fortran IDE. It features a plugin architecture
# that supports multiple compilers (GCC, Clang, MSVC) and
# debuggers.
#
# Code::Blocks is available in the official Arch repos and is
# installed via pacman.
#
# This is an optional module because it is a GUI IDE that is
# mainly relevant to developers who prefer a graphical
# environment over terminal-based editors for C/C++ work.
# ============================================================
set -euo pipefail
# Load shared logging helpers from the dotfiles lib
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
# ── codeblocks (official repos) ───────────────────────────────────────────────
# The codeblocks package includes the IDE, its built-in GCC compiler plugin,
# and the wxWidgets GUI toolkit it depends on.
log "Installing Code::Blocks..."
sudo pacman -S --noconfirm --needed codeblocks
log "Code::Blocks installed."

View File

@ -1,7 +1,28 @@
#!/bin/bash
# ============================================================
# croc.sh — croc peer-to-peer file transfer tool
# ============================================================
# Installs croc, a simple command-line tool for sending files
# and folders between two computers using end-to-end encryption
# and a relay server. Unlike scp, croc works through NAT and
# firewalls without needing port forwarding, making it ideal
# for ad-hoc transfers on the local network or the internet.
#
# Usage: croc send <file> (sender)
# croc <code-phrase> (receiver)
#
# croc is available in the official Arch repos.
# This is an optional module because it is only needed when
# transferring files to/from machines that don't share a
# common filesystem or SSH access.
# ============================================================
set -euo pipefail
# Load shared logging helpers from the dotfiles lib
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
# ── croc (official repos) ─────────────────────────────────────────────────────
log "Installing croc (file transfer)..."
sudo pacman -S --noconfirm --needed croc
log "croc installed."

View File

@ -1,10 +1,39 @@
#!/bin/bash
# ============================================================
# db-clients.sh — CLI database clients (pgcli, mycli)
# ============================================================
# Installs interactive, feature-rich CLI clients for the two
# most common open-source relational databases:
#
# pgcli — PostgreSQL client with syntax highlighting, auto-
# completion, and multi-line editing (official repos)
# mycli — MySQL / MariaDB client with the same UX improvements
# (AUR only, as the upstream package is not in the
# official Arch repos)
#
# Both tools are built on Python's prompt-toolkit library and
# offer a significantly better interactive experience than the
# stock `psql` and `mysql` CLIs.
#
# This is an optional module because database clients are only
# needed on machines used for database development or
# administration.
# ============================================================
set -euo pipefail
# Load shared logging helpers from the dotfiles lib
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
# ── pgcli (official repos) ────────────────────────────────────────────────────
# pgcli: PostgreSQL client with auto-complete, syntax highlighting, and
# multi-line query editing. It also shows the query explain plan inline.
log "Installing pgcli..."
sudo pacman -S --noconfirm --needed pgcli
# ── mycli (AUR) ──────────────────────────────────────────────────────────────
# mycli: MySQL / MariaDB CLI with identical UX improvements to pgcli.
# Available from AUR only because it is not packaged in the official repos.
log "Installing mycli (AUR)..."
yay -S --answerdiff None --answerclean All --noconfirm mycli
log "DB clients installed."

View File

@ -1,10 +1,42 @@
#!/bin/bash
# ============================================================
# disk-recovery.sh — Disk recovery and flash-testing tools
# ============================================================
# Installs two complementary data-recovery/storage-testing tools:
#
# ddrescue — GNU ddrescue: robust block-level disk imaging tool
# that retries bad sectors and creates a recovery log
# so interrupted rescues can be resumed. Available in
# the official Arch repos.
#
# f3 — Fight Flash Fraud: tests USB drives and SD cards for
# counterfeit capacity by writing and verifying data.
# Available from AUR only.
#
# These are optional because they are specialist tools most users
# only reach for when something has gone wrong (failing drive,
# suspected fake flash storage).
# ============================================================
set -euo pipefail
# Load shared logging helpers from the dotfiles lib
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
# ── ddrescue (official repos) ─────────────────────────────────────────────────
# ddrescue copies data from a failing or damaged disk to an image file,
# intelligently skipping bad sectors and retrying them later. The recovery
# log (mapfile) enables resuming after an interruption without re-reading
# already-good sectors. Preferred over dd for data recovery because it
# maximises the amount of data recovered from a partially readable drive.
log "Installing ddrescue..."
sudo pacman -S --noconfirm --needed ddrescue
# ── f3 (AUR) ─────────────────────────────────────────────────────────────────
# f3 (Fight Flash Fraud) reveals counterfeit storage devices that report a
# larger capacity than they actually have. It works by filling the device
# with unique data then reading it back to find where corruption starts.
# Useful before trusting a newly purchased USB drive or SD card.
log "Installing f3 (AUR)..."
yay -S --answerdiff None --answerclean All --noconfirm f3
log "Disk recovery tools installed."

View File

@ -1,13 +1,46 @@
#!/bin/bash
# ============================================================
# docker.sh — Docker container runtime
# ============================================================
# Installs Docker Engine and Docker Compose, enables the Docker
# daemon at boot, and adds the current user to the `docker`
# group so containers can be managed without sudo.
#
# docker — the Docker daemon + CLI
# docker-compose — the Compose v2 plugin for multi-container apps
#
# Why not Podman instead? Podman is the preferred rootless
# alternative (see podman.sh), but Docker is still the most
# widely documented container runtime and some workflows
# (Portainer, legacy scripts, certain CI tools) assume it.
#
# Note: Docker's daemon runs as root, which has security
# implications. The docker group grants equivalent root access;
# this is intentional for developer workstations.
# ============================================================
set -euo pipefail
# Load shared logging helpers from the dotfiles lib
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
# ── Packages ──────────────────────────────────────────────────────────────────
# docker : the Docker Engine daemon (dockerd) + CLI client (docker)
# docker-compose : Docker Compose plugin; provides `docker compose` subcommand
log "Installing Docker and Docker Compose..."
sudo pacman -S --noconfirm --needed docker docker-compose
# ── Enable Docker daemon ──────────────────────────────────────────────────────
# docker.service starts the Docker daemon at boot. Without this, running
# `docker` commands would fail with "Cannot connect to the Docker daemon".
log "Enabling Docker service..."
sudo systemctl enable docker.service
# ── Add current user to docker group ─────────────────────────────────────────
# By default, only root can communicate with the Docker socket at
# /var/run/docker.sock. Adding the user to the `docker` group grants
# access without requiring `sudo docker ...` every time.
# The group membership takes effect at the next login (a new login session
# is required to re-evaluate group membership).
log "Adding $USER to docker group..."
sudo usermod -aG docker "$USER"

View File

@ -1,8 +1,34 @@
#!/bin/bash
# ============================================================
# doom.sh — Chocolate Doom + Freedoom game data
# ============================================================
# Installs a faithful Doom port and free game data so the
# classic first-person shooter can be played out of the box
# without commercial game files.
#
# chocolate-doom : a vanilla-accurate source port that
# reproduces the original Doom engine
# behaviour as closely as possible, including
# the original renderer, bugs, and limits.
#
# freedoom : free/libre replacement game data (IWAD)
# for both Doom 1 (freedoom1.wad) and Doom 2
# (freedoom2.wad). Required to run Chocolate
# Doom without owning the commercial IWADs.
#
# Users who own the original Doom IWADs (doom.wad, doom2.wad)
# can use those instead of or alongside freedoom.
#
# This is an optional gaming module; it is not installed by
# default because not all users want games on their system.
# ============================================================
set -euo pipefail
# Load shared logging helpers from the dotfiles lib
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
# chocolate-doom: faithful vanilla Doom port; freedoom: free game data (playable without IWADs)
log "Installing Chocolate Doom and Freedoom data..."
sudo pacman -S --noconfirm --needed chocolate-doom freedoom
log "Doom installed."

View File

@ -1,9 +1,42 @@
#!/bin/bash
# ============================================================
# ffmpeg.sh — FFmpeg multimedia framework + GStreamer codecs
# ============================================================
# Installs the FFmpeg CLI toolkit, a thumbnailer plugin for
# file managers, and a comprehensive set of GStreamer codec
# plugins. Together these ensure that the desktop can play,
# convert, and generate thumbnails for virtually any audio or
# video format.
#
# Packages:
# ffmpeg — the core FFmpeg encoder/decoder library
# and CLI tools (ffmpeg, ffprobe, ffplay)
# ffmpegthumbnailer — a thumbnail generator that integrates
# with file managers (Thunar, Nautilus,
# Dolphin) to show video frame previews
# gst-libav — GStreamer plugin wrapping FFmpeg/libav
# codecs (H.264, AAC, MP3, etc.)
# gst-plugins-good — stable GStreamer plugins (VP8/9, FLAC,
# Ogg, ALSA, V4L2, etc.)
# gst-plugins-bad — plugins not yet meeting quality bar
# but widely used (H.265/HEVC, Opus, AV1,
# MPEG-TS, etc.)
# gst-plugins-ugly — legally ambiguous plugins (MP3, MPEG-2
# video, etc.) that may require patent
# licences in some jurisdictions
#
# This is an optional module because the base system includes
# only minimal codec support; users who want full multimedia
# playback should install this module.
# ============================================================
set -euo pipefail
# Load shared logging helpers from the dotfiles lib
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing FFmpeg extras (thumbnailer + GStreamer codecs)..."
sudo pacman -S --noconfirm --needed \
ffmpeg ffmpegthumbnailer \
gst-libav gst-plugins-good gst-plugins-bad gst-plugins-ugly
log "FFmpeg extras installed."

View File

@ -1,9 +1,35 @@
#!/bin/bash
# ============================================================
# firefox.sh — Mozilla Firefox browser (Flatpak)
# ============================================================
# Installs Firefox via Flatpak rather than from the official
# Arch repos or AUR. The Flatpak version is sandboxed, ships
# with its own bundled libraries (avoiding libpulse/libx11
# version conflicts), and auto-updates independently of the
# system package manager.
#
# Firefox is offered as an optional module because some users
# prefer a different browser (Chromium, LibreWolf, Zen) as
# their primary, or they use only a Wayland-native browser;
# installing multiple browsers is intentional for testing or
# compartmentalisation.
# ============================================================
set -euo pipefail
# Load shared logging helpers and Flatpak utility functions
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Firefox (Flatpak)..."
# ensure_flatpak: checks that Flatpak and the Flathub remote are configured;
# idempotent — safe to call multiple times.
ensure_flatpak
# Install from Flathub. -y skips interactive confirmation.
flatpak install -y flathub org.mozilla.firefox
# apply_flatpak_theme: injects the cyberqueer GTK theme so Firefox's native
# file-open dialogs and context menus match the rest of the desktop.
apply_flatpak_theme "org.mozilla.firefox"
log "Firefox installed."

View File

@ -1,23 +1,69 @@
#!/bin/bash
# ============================================================
# freeipa-client.sh — FreeIPA client enrolment installer
# ============================================================
# Installs the packages needed to join this machine to a
# FreeIPA domain and then guides the user through three
# enrolment paths via a dialog(1) TUI:
#
# answerfile — reads a pre-filled JSON file produced by the
# freeipa-server.sh installer (recommended for
# scripted/repeatable deployments)
# manual — interactive dialog prompts for every option
# skip — installs packages only; enrol later manually
#
# Packages installed:
# sssd — System Security Services Daemon; provides
# PAM/NSS integration for IPA-managed users
# cyrus-sasl-gssapi — SASL GSSAPI mechanism for Kerberos auth
# openldap — LDAP utilities (ldapsearch, etc.)
# krb5 — Kerberos 5 libraries and kinit/klist tools
# oddjob — D-Bus service that auto-creates home dirs
# on first login (via pam_oddjob_mkhomedir)
# freeipa-client — AUR package providing ipa-client-install
#
# This is an optional module because FreeIPA client enrolment is
# only relevant on managed/enterprise machines that are members
# of a FreeIPA domain.
# ============================================================
set -euo pipefail
# Load shared logging helpers from the dotfiles lib
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Starting FreeIPA client installer..."
# Resolve the absolute path of this script's directory so all relative
# references (to FreeipaAnsible scripts and answerfiles) remain correct
# regardless of where the installer TUI calls this module from.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
FREEIPA_DIR="$SCRIPT_DIR/../../FreeipaAnsible"
# Optional server-generated enrolment wrapper script
CLIENT_SCRIPT="$FREEIPA_DIR/freeipa-client.sh"
# Default answerfile path — the server installer writes this file with
# the domain/realm/server already filled in, so clients only need to
# add the admin password.
DEFAULT_AF="$FREEIPA_DIR/freeipa-client-answerfile.json"
# Defaults — match freeipa-client-answerfile.json (sed-replaced by server installer)
# Default values matching the home lab domain; the server installer
# sed-replaces these in the distributed client scripts so clients
# always get the correct values without any manual editing.
DEF_DOMAIN="freeipa.abdelbaki.eu"
DEF_REALM="FREEIPA.ABDELBAKI.EU"
DEF_SERVER="freeipa.abdelbaki.eu"
# ── Packages ──────────────────────────────────────────────────────────────────
echo "[+] Installing FreeIPA client packages..."
# pacman (without sudo) because this script may be run as root by the TUI
# sssd : provides NSS/PAM lookups against IPA LDAP + Kerberos
# cyrus-sasl-gssapi : GSSAPI mechanism; needed for LDAP binds with Kerberos tickets
# openldap : ldapsearch and other LDAP client utilities
# krb5 : Kerberos 5 runtime; kinit, klist, kdestroy
# oddjob : D-Bus service that runs pam_oddjob_mkhomedir on first login
pacman -S --noconfirm --needed sssd cyrus-sasl-gssapi openldap krb5 oddjob
# freeipa-client provides ipa-client-install (the official FreeIPA enrolment
# tool). It is AUR-only on Arch; gracefully degrade if yay is absent.
if command -v yay &>/dev/null; then
echo "[+] Installing freeipa-client (AUR)..."
yay -S --noconfirm --needed freeipa-client
@ -26,11 +72,18 @@ else
echo " Install yay then run: yay -S --needed freeipa-client"
fi
# Enable SSSD now so it starts at boot after enrolment.
# "|| true" suppresses the error if the service unit doesn't exist yet
# (it will exist after freeipa-client is installed).
systemctl enable sssd.service 2>/dev/null || true
# Ensure dialog is available for the TUI; install it on-the-fly if missing.
command -v dialog &>/dev/null || pacman -S --noconfirm --needed dialog
# ── Dialog theme ──────────────────────────────────────────────────────────────
# Apply the same magenta-on-black cyberqueer colour scheme used throughout
# the dotfiles TUI installers so all dialog screens look consistent.
# We only write a temporary dialogrc if one isn't already set in the environment.
if [[ -z "${DIALOGRC:-}" ]] || [[ ! -f "${DIALOGRC:-/dev/null}" ]]; then
_TMP_D=$(mktemp -d)
trap 'rm -rf "$_TMP_D"' EXIT
@ -68,6 +121,9 @@ fi
BT="FreeIPA Client Setup"
T=$(mktemp); trap 'rm -f "$T"' EXIT
# ── dialog helper wrappers ────────────────────────────────────────────────────
# These thin wrappers normalise the fd-swap trick needed to capture dialog
# output (dialog writes to stderr, so we swap stdout/stderr with 3>&1 1>&2 2>&3).
d() { dialog --backtitle "$BT" "$@" 3>&1 1>&2 2>&3; }
input(){ d --title " $1 " --inputbox "$2" 10 64 "$3"; }
pass() { d --title " $1 " --passwordbox "$2" 10 64; }
@ -75,6 +131,8 @@ yn() { d --title " $1 " --yesno "$2" 7 64; }
msg() { d --title " FreeIPA Client " --msgbox "$1" 10 64; }
# ── Enrollment choice ─────────────────────────────────────────────────────────
# Present three options: answerfile, manual, or skip.
# Default to "skip" if the user presses Escape or cancels.
CHOICE=$(d --title " FreeIPA Client Enrollment " \
--menu "\n Packages installed.\n How would you like to enroll this host?\n" \
13 64 3 \
@ -82,10 +140,14 @@ CHOICE=$(d --title " FreeIPA Client Enrollment " \
"manual" "Enter enrollment data manually" \
"skip" "Skip — enroll later") || CHOICE="skip"
# ── Helpers ───────────────────────────────────────────────────────────────────
# ── run_enroll helper ─────────────────────────────────────────────────────────
# Runs the enrolment either via the server-generated freeipa-client.sh wrapper
# (preferred, because it already has the correct domain/realm baked in) or by
# falling back to calling ipa-client-install directly with the same arguments.
run_enroll() {
local args=("$@")
if [[ -x "$CLIENT_SCRIPT" ]]; then
# Preferred path: use the pre-generated client script from the server
exec "$CLIENT_SCRIPT" "${args[@]}"
else
# Fall back to ipa-client-install directly
@ -94,6 +156,7 @@ run_enroll() {
local mkhomedir=true sudo_=true dns=true fido2=false
declare -a fido2_users=()
# Parse our internal argument format and translate to ipa-client-install flags
for ((i=0; i<${#args[@]}; i++)); do
case "${args[$i]}" in
--domain) dom="${args[$((i+1))]}"; ((i++)) ;;
@ -111,6 +174,7 @@ run_enroll() {
esac
done
# Build the ipa-client-install command from parsed values
[[ -n "$dom" ]] && cmd+=(--domain "$dom")
[[ -n "$rlm" ]] && cmd+=(--realm "$rlm")
[[ -n "$srv" ]] && cmd+=(--server "$srv")
@ -125,14 +189,18 @@ run_enroll() {
}
# ── Answerfile mode ───────────────────────────────────────────────────────────
# The answerfile is a JSON file pre-generated by freeipa-server.sh with domain,
# realm, and server already filled in. The user only needs to add the password.
if [[ "$CHOICE" == "answerfile" ]]; then
AF_PATH=$(input "Answerfile path" \
"Path to the JSON answerfile:" \
"$DEFAULT_AF") || { msg " Enrollment cancelled."; exit 0; }
# Use the default path if the user pressed Enter without typing a path
[[ -z "$AF_PATH" ]] && AF_PATH="$DEFAULT_AF"
if [[ ! -f "$AF_PATH" ]]; then
# Offer to create a template answerfile so the user can fill it in
if yn "Create answerfile" \
" '$AF_PATH' does not exist.\n Create it with default values?"; then
mkdir -p "$(dirname "$AF_PATH")"
@ -160,16 +228,19 @@ AFEOF
fi
fi
# Clear the dialog UI before running ipa-client-install (which prints to stdout)
clear
run_enroll --answerfile "$AF_PATH"
fi
# ── Manual mode ───────────────────────────────────────────────────────────────
# Collect all enrolment parameters interactively via dialog prompts.
if [[ "$CHOICE" == "manual" ]]; then
DOMAIN=$(input "IPA Domain" "FreeIPA domain:" "$DEF_DOMAIN") \
|| { msg " Enrollment cancelled."; exit 0; }
DOMAIN="${DOMAIN:-$DEF_DOMAIN}"
# Kerberos realm is conventionally the domain uppercased; pre-fill that guess
DEF_REALM_CALC="${DOMAIN^^}"
REALM=$(input "Kerberos Realm" "Kerberos realm (usually domain uppercased):" \
"$DEF_REALM_CALC") || REALM="$DEF_REALM_CALC"
@ -179,6 +250,7 @@ if [[ "$CHOICE" == "manual" ]]; then
|| { msg " Enrollment cancelled."; exit 0; }
SERVER="${SERVER:-$DOMAIN}"
# Default to the current machine's FQDN so the user can just press Enter
DEF_HOST=$(hostname -f 2>/dev/null || hostname)
HOST=$(input "This Host FQDN" "Hostname to register (leave blank = current):" \
"$DEF_HOST") || HOST="$DEF_HOST"
@ -186,6 +258,7 @@ if [[ "$CHOICE" == "manual" ]]; then
PRINCIPAL=$(input "Admin Principal" "IPA admin principal:" "admin") || PRINCIPAL="admin"
PRINCIPAL="${PRINCIPAL:-admin}"
# Use passwordbox so the password is not echoed to the terminal
PASSWORD=$(pass "Admin Password" "Password for $PRINCIPAL@$REALM:") \
|| { msg " Enrollment cancelled."; exit 0; }
[[ -z "$PASSWORD" ]] && { msg " Password is required."; exit 1; }
@ -202,6 +275,7 @@ if [[ "$CHOICE" == "manual" ]]; then
)
[[ -n "$NTP" ]] && ARGS+=(--ntp-server "$NTP")
# Optional features: each yes/no dialog adds or omits the corresponding flag
yn "Home Directories" " Auto-create home directories on first login?" \
&& true || ARGS+=(--no-mkhomedir)
@ -211,12 +285,14 @@ if [[ "$CHOICE" == "manual" ]]; then
yn "DNS Update" " Register this host's IP in IPA DNS?" \
&& true || ARGS+=(--no-dns-update)
# FIDO2/WebAuthn hardware token support (requires libfido2 + pamu2fcfg)
if yn "FIDO2" " Enable FIDO2/WebAuthn authentication?"; then
ARGS+=(--fido2)
FIDO2_USERS=$(input "FIDO2 Users" \
"Usernames to enable FIDO2 for (comma-separated, blank = all):" "") \
|| FIDO2_USERS=""
if [[ -n "$FIDO2_USERS" ]]; then
# Split comma-separated list and strip whitespace from each username
IFS=',' read -ra _U <<< "$FIDO2_USERS"
for u in "${_U[@]}"; do
u="${u// /}"
@ -230,6 +306,7 @@ if [[ "$CHOICE" == "manual" ]]; then
fi
# ── Skip ──────────────────────────────────────────────────────────────────────
# Packages are installed; print instructions for later manual enrolment.
echo ""
echo "[✓] FreeIPA client packages installed."
echo ""

View File

@ -1,9 +1,12 @@
#!/bin/bash
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
set -euo pipefail
# Load shared log/skip/warn/err helpers from the installer library.
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Geany (Flatpak)..."
ensure_flatpak
flatpak install -y flathub org.geany.Geany
apply_flatpak_theme "org.geany.Geany"
log "Installing Geany and plugins..."
# geany: lightweight GTK text editor / basic IDE with syntax highlighting.
# geany-plugins: meta-package of community plugins (project manager, Git integration,
# spell check, etc.) that ship separately from the core editor.
sudo pacman -S --noconfirm --needed geany geany-plugins
log "Geany installed."

View File

@ -1,9 +1,11 @@
#!/bin/bash
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
set -euo pipefail
# Load shared log/skip/warn/err helpers from the installer library.
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing GIMP (Flatpak)..."
ensure_flatpak
flatpak install -y flathub org.gimp.GIMP
apply_flatpak_theme "org.gimp.GIMP"
log "Installing GIMP..."
# Install GIMP (GNU Image Manipulation Program) from the official Arch repos.
# --noconfirm skips interactive prompts; --needed avoids reinstalling if current.
sudo pacman -S --noconfirm --needed gimp
log "GIMP installed."

View File

@ -1,7 +1,11 @@
#!/bin/bash
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
set -euo pipefail
# Load shared log/skip/warn/err helpers from the installer library.
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Gnuplot..."
# gnuplot is a portable command-line graphing and plotting tool.
# It is available in the official Arch repos; --needed keeps the install idempotent.
sudo pacman -S --noconfirm --needed gnuplot
log "Gnuplot installed."

View File

@ -1,7 +1,13 @@
#!/bin/bash
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
set -euo pipefail
# Load shared log/skip/warn/err helpers from the installer library.
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Himalaya (AUR)..."
# himalaya-bin is the pre-compiled binary release of the Himalaya CLI email client.
# The -bin variant is preferred over building from source because compiling the full
# Rust workspace is slow and requires a complete Rust toolchain.
# --answerdiff None / --answerclean All suppress interactive PKGBUILD/clean prompts.
yay -S --answerdiff None --answerclean All --noconfirm himalaya-bin
log "Himalaya installed."

View File

@ -1,7 +1,11 @@
#!/bin/bash
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
set -euo pipefail
# Load shared log/skip/warn/err helpers from the installer library.
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing ImageMagick..."
# ImageMagick provides the convert/mogrify/identify CLI tools for image manipulation,
# format conversion, and batch processing. It is in the official Arch repos.
sudo pacman -S --noconfirm --needed imagemagick
log "ImageMagick installed."

View File

@ -1,9 +1,11 @@
#!/bin/bash
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
set -euo pipefail
# Load shared log/skip/warn/err helpers from the installer library.
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Inkscape (Flatpak)..."
ensure_flatpak
flatpak install -y flathub org.inkscape.Inkscape
apply_flatpak_theme "org.inkscape.Inkscape"
log "Installing Inkscape..."
# Inkscape is a vector graphics editor (SVG-native); available in the official repos.
# --noconfirm skips interactive prompts; --needed avoids reinstalling if current.
sudo pacman -S --noconfirm --needed inkscape
log "Inkscape installed."

View File

@ -1,12 +1,12 @@
#!/bin/bash
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
set -euo pipefail
# Load shared log/skip/warn/err helpers from the installer library.
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing kubectl (pacman)..."
sudo pacman -S --noconfirm --needed kubectl
log "Installing Podman Desktop (Flatpak)..."
ensure_flatpak
flatpak install -y flathub io.podman_desktop.PodmanDesktop
apply_flatpak_theme "io.podman_desktop.PodmanDesktop"
log "Installing Kubernetes tools (kubectl, podman-desktop)..."
# kubectl: official Kubernetes CLI for interacting with clusters.
# podman-desktop: native GUI for managing Podman containers and Kubernetes contexts.
# Both are available in the official Arch repos; --needed keeps the run idempotent.
sudo pacman -S --noconfirm --needed kubectl podman-desktop
log "Kubernetes tools installed."

View File

@ -1,9 +1,11 @@
#!/bin/bash
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
set -euo pipefail
# Load shared log/skip/warn/err helpers from the installer library.
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Kate (Flatpak)..."
ensure_flatpak
flatpak install -y flathub org.kde.kate
apply_flatpak_theme "org.kde.kate"
log "Installing Kate..."
# Kate is the KDE Advanced Text Editor; available in the official Arch repos.
# --noconfirm skips interactive prompts; --needed avoids reinstalling if current.
sudo pacman -S --noconfirm --needed kate
log "Kate installed."

View File

@ -1,9 +1,11 @@
#!/bin/bash
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
set -euo pipefail
# Load shared log/skip/warn/err helpers from the installer library.
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Kdenlive (Flatpak)..."
ensure_flatpak
flatpak install -y flathub org.kde.kdenlive
apply_flatpak_theme "org.kde.kdenlive"
log "Installing Kdenlive..."
# Kdenlive is the KDE non-linear video editor; available in the official Arch repos.
# --noconfirm skips interactive prompts; --needed avoids reinstalling if current.
sudo pacman -S --noconfirm --needed kdenlive
log "Kdenlive installed."

View File

@ -1,9 +1,11 @@
#!/bin/bash
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
set -euo pipefail
# Load shared log/skip/warn/err helpers from the installer library.
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Krita (Flatpak)..."
ensure_flatpak
flatpak install -y flathub org.kde.krita
apply_flatpak_theme "org.kde.krita"
log "Installing Krita..."
# Krita is a professional digital painting application; available in the official repos.
# --noconfirm skips interactive prompts; --needed avoids reinstalling if current.
sudo pacman -S --noconfirm --needed krita
log "Krita installed."

Some files were not shown because too many files have changed in this diff Show More