chore: merge comment annotations from agent worktrees into main
133 files annotated with inline comments and section headers across: - setup scripts (install, core, packages, shell, DEs, apps) - hyprlua/hypr configs (hyprland.lua, hypridle, hyprlock, monitorhandler) - niri config KDL modules - dotfiles (.bashrc, .zshrc, starship.toml, colors.conf, etc.) - hyprlua scripts and waybar scripts - archiso build scripts Monitor configuration (hyprmoncfg / monitors.lua / binds.lua) untouched. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>main
commit
523ec416e8
92
.bashrc
92
.bashrc
|
|
@ -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
|
[[ $- != *i* ]] && return
|
||||||
|
|
||||||
|
# ─── Core aliases ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Colorize ls and grep output — file types and match highlights stand out visually.
|
||||||
alias ls='ls --color=auto'
|
alias ls='ls --color=auto'
|
||||||
alias grep='grep --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]\$ '
|
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 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 .."
|
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 m="micro"
|
||||||
alias sm="sudo micro"
|
alias sm="sudo micro" # Elevated edit for system files owned by root
|
||||||
|
|
||||||
alias gita="git add ."
|
# ─── Git shortcuts ─────────────────────────────────────────────────────────────
|
||||||
alias gitc="git commit -m"
|
|
||||||
alias gitp="git push"
|
|
||||||
|
|
||||||
|
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/"
|
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() {
|
function gitf() {
|
||||||
|
|
||||||
if [ -z $1 ]; then
|
if [ -z $1 ]; then
|
||||||
|
|
@ -35,31 +64,66 @@ function gitf() {
|
||||||
fi
|
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"
|
alias icat="kitten icat"
|
||||||
|
|
||||||
|
# Two ways to clear the screen — cls is familiar muscle memory from Windows cmd.
|
||||||
alias cls="clear"
|
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 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"
|
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"
|
alias t="wd"
|
||||||
|
|
||||||
|
# Filtered listing — pipe a long-format listing through grep for fast file search.
|
||||||
alias lgrep="l | grep"
|
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() {
|
function y() {
|
||||||
local tmp="$(mktemp -t "yazi-cwd.XXXXXX")" cwd
|
local tmp="$(mktemp -t "yazi-cwd.XXXXXX")" cwd # temp file to capture yazi's last dir
|
||||||
yazi "$@" --cwd-file="$tmp"
|
yazi "$@" --cwd-file="$tmp" # run yazi; it writes final dir to $tmp
|
||||||
if cwd="$(command cat -- "$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then
|
if cwd="$(command cat -- "$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then
|
||||||
builtin cd -- "$cwd"
|
builtin cd -- "$cwd" # apply the directory change to this shell
|
||||||
fi
|
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)"
|
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"
|
. "$HOME/.cargo/env"
|
||||||
|
|
|
||||||
17
.vimrc
17
.vimrc
|
|
@ -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
|
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
|
colorscheme cyberqueer.nvim
|
||||||
|
|
|
||||||
231
.zshrc
231
.zshrc
|
|
@ -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 )
|
GREPOUTPUT=$( grep --help 2>&1 )
|
||||||
RESULT=$( echo $GREPOUTPUT | grep gnu )
|
RESULT=$( echo $GREPOUTPUT | grep gnu )
|
||||||
if [ "${RESULT}" != "" ]; then
|
if [ "${RESULT}" != "" ]; then
|
||||||
|
|
@ -5,6 +25,9 @@ if [ "${RESULT}" != "" ]; then
|
||||||
# If you come from bash you might have to change your $PATH.
|
# If you come from bash you might have to change your $PATH.
|
||||||
# export PATH=$HOME/bin:$HOME/.local/bin:/usr/local/bin:$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.
|
# Path to your Oh My Zsh installation.
|
||||||
export ZSH="$HOME/.oh-my-zsh"
|
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,
|
# 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
|
# to know which specific one was loaded, run: echo $RANDOM_THEME
|
||||||
# See https://github.com/ohmyzsh/ohmyzsh/wiki/Themes
|
# See https://github.com/ohmyzsh/ohmyzsh/wiki/Themes
|
||||||
|
# robbyrussell is the classic OMZ default; starship overrides it at the bottom.
|
||||||
ZSH_THEME="robbyrussell"
|
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"
|
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 {
|
function lk {
|
||||||
cd "$(walk "$@")"
|
cd "$(walk "$@")"
|
||||||
}
|
}
|
||||||
|
|
@ -73,30 +103,35 @@ function lk {
|
||||||
# Would you like to use another custom folder than $ZSH/custom?
|
# Would you like to use another custom folder than $ZSH/custom?
|
||||||
# ZSH_CUSTOM=/path/to/new-custom-folder
|
# 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?
|
# Which plugins would you like to load?
|
||||||
# Standard plugins can be found in $ZSH/plugins/
|
# Standard plugins can be found in $ZSH/plugins/
|
||||||
# Custom plugins may be added to $ZSH_CUSTOM/plugins/
|
# Custom plugins may be added to $ZSH_CUSTOM/plugins/
|
||||||
# Example format: plugins=(rails git textmate ruby lighthouse)
|
# Example format: plugins=(rails git textmate ruby lighthouse)
|
||||||
# Add wisely, as too many plugins slow down shell startup.
|
# Add wisely, as too many plugins slow down shell startup.
|
||||||
plugins=( git
|
plugins=( git # git aliases (gst, gco, ga, gp, etc.)
|
||||||
zsh-syntax-highlighting
|
zsh-syntax-highlighting # colorizes valid commands green, errors red (installed via zshplugins.sh)
|
||||||
zsh-autosuggestions
|
zsh-autosuggestions # ghost-text completions based on command history (installed via zshplugins.sh)
|
||||||
wd
|
wd # warp directory: bookmark dirs with 'wd add <name>', jump with 'wd <name>'
|
||||||
archlinux
|
archlinux # pacman/yay shortcuts (pacin, pacrem, etc.)
|
||||||
git
|
git # duplicate entry — harmless but redundant
|
||||||
git-auto-fetch
|
git-auto-fetch # automatically runs 'git fetch' in the background in git repos
|
||||||
nmap
|
nmap # completions for nmap
|
||||||
perms
|
perms # helper functions for setting file permissions
|
||||||
zsh-interactive-cd
|
zsh-interactive-cd # interactive fuzzy cd using fzf
|
||||||
zsh-navigation-tools
|
zsh-navigation-tools # panel-style history/dir navigation widgets
|
||||||
z
|
z # jump to frecent directories: 'z projectname' jumps to most visited match
|
||||||
ufw
|
ufw # completions for ufw (Uncomplicated Firewall)
|
||||||
web-search
|
web-search # open web searches from the terminal: 'google foo'
|
||||||
timer
|
timer # shows command execution time in the prompt for long-running commands
|
||||||
sudo
|
sudo # press Esc twice to prepend 'sudo' to the previous command
|
||||||
taskwarrior
|
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
|
source $ZSH/oh-my-zsh.sh
|
||||||
|
|
||||||
# User configuration
|
# User configuration
|
||||||
|
|
@ -127,40 +162,73 @@ source $ZSH/oh-my-zsh.sh
|
||||||
# Example aliases
|
# Example aliases
|
||||||
# alias zshconfig="mate ~/.zshrc"
|
# alias zshconfig="mate ~/.zshrc"
|
||||||
# alias ohmyzsh="mate ~/.oh-my-zsh"
|
# 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'
|
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
|
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
|
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
|
export TERM=xterm-256color
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Core aliases ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Colorize ls and grep output for readability.
|
||||||
alias ls='ls --color=auto'
|
alias ls='ls --color=auto'
|
||||||
alias grep='grep --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 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"
|
alias ldsk="lsblk -o+FSTYPE,MODEL,UUID"
|
||||||
|
|
||||||
|
# Quick parent-directory jump.
|
||||||
alias ..="cd .."
|
alias ..="cd .."
|
||||||
|
|
||||||
alias m="micro"
|
# ─── Editor aliases ────────────────────────────────────────────────────────────
|
||||||
alias sm="sudo micro"
|
|
||||||
alias e="edit-in-kitty"
|
alias m="micro" # micro: simple terminal editor, good for quick config edits
|
||||||
alias et="edit-in-kitty --type=tab"
|
alias sm="sudo micro" # sudo micro: edit system-owned files
|
||||||
alias ek="edit-in-kitty --type=window"
|
|
||||||
alias ew="kitty --detach micro"
|
# 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 ."
|
alias ex="thunar ."
|
||||||
|
|
||||||
|
# fast-ssh: a wrapper around ssh with fuzzy host selection from ~/.ssh/config.
|
||||||
alias fs="fast-ssh"
|
alias fs="fast-ssh"
|
||||||
|
|
||||||
alias gita="git add ."
|
# ─── Git aliases ───────────────────────────────────────────────────────────────
|
||||||
alias gitc="git commit -m"
|
|
||||||
alias gitp="git push"
|
|
||||||
alias gitg="git pull"
|
|
||||||
alias gitfuck="git commit --amend -m"
|
|
||||||
|
|
||||||
|
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() {
|
function gitf() {
|
||||||
|
|
||||||
if [ -z $1 ]; then
|
if [ -z $1 ]; then
|
||||||
|
|
@ -172,80 +240,145 @@ function gitf() {
|
||||||
git push
|
git push
|
||||||
fi
|
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"
|
alias ks="kitten ssh"
|
||||||
|
|
||||||
#function s() {
|
#function s() {
|
||||||
# alacritty -e sh -c "ssh $1" &
|
# 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"
|
alias serial="sudo tio -a latest"
|
||||||
|
|
||||||
|
# wd: warp directory bookmarks. 't' as an alias makes it one keystroke.
|
||||||
alias t="wd"
|
alias t="wd"
|
||||||
|
|
||||||
|
# Terminal weather report via wttr.in (ANSI art, no browser needed).
|
||||||
alias weather="curl https://wttr.in/"
|
alias weather="curl https://wttr.in/"
|
||||||
|
|
||||||
|
# Pipe detailed listing through grep for quick in-directory file search.
|
||||||
alias lgrep="l | grep"
|
alias lgrep="l | grep"
|
||||||
alias lg="lgrep"
|
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 treegrep="tree -fi | grep"
|
||||||
alias tg="treegrep"
|
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"
|
alias cpwd="pwd | wl-copy"
|
||||||
|
|
||||||
|
# v/vi: use neovim as the primary $VISUAL editor instead of legacy vim.
|
||||||
alias v="nvim"
|
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 vi="nvim"
|
||||||
|
|
||||||
alias pyr="python"
|
# sv: sudoedit — opens a file as root in $SUDO_EDITOR (nvim) using a safe
|
||||||
alias pynowr='python -W "ignore"'
|
# 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()
|
function dbc()
|
||||||
{
|
{
|
||||||
distrobox create --image "${1}:latest" --name $2
|
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()
|
function dbt()
|
||||||
{
|
{
|
||||||
distrobox create --image "${1}:latest" --name $1
|
distrobox create --image "${1}:latest" --name $1
|
||||||
distrobox enter $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() {
|
function y() {
|
||||||
local tmp="$(mktemp -t "yazi-cwd.XXXXXX")" cwd
|
local tmp="$(mktemp -t "yazi-cwd.XXXXXX")" cwd # temp file for yazi's final cwd
|
||||||
yazi "$@" --cwd-file="$tmp"
|
yazi "$@" --cwd-file="$tmp" # run yazi with cwd reporting enabled
|
||||||
if cwd="$(command cat -- "$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then
|
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
|
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() {
|
function n() {
|
||||||
cd $1
|
cd $1
|
||||||
echo $(pwd)
|
echo $(pwd)
|
||||||
ls -la
|
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; }
|
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)"
|
eval "$(starship init zsh)"
|
||||||
|
|
||||||
|
# ─── PATH additions ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Spicetify: Spotify theme/extension manager. Its CLI lives in ~/.spicetify.
|
||||||
export PATH=$PATH:$HOME/.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"
|
export NVM_DIR="$HOME/.nvm"
|
||||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads 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
|
[ -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
|
export SUDO_EDITOR=/usr/bin/nvim
|
||||||
|
|
||||||
# Created by `pipx` on 2025-05-12 08:58:28
|
# 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="$PATH:~/.local/bin"
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
|
|
||||||
|
|
@ -110,12 +110,15 @@
|
||||||
|
|
||||||
# A TCP port number the daemon will listen on.
|
# A TCP port number the daemon will listen on.
|
||||||
# Default: disabled
|
# 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
|
TCPSocket 3310
|
||||||
|
|
||||||
# By default clamd binds to INADDR_ANY.
|
# By default clamd binds to INADDR_ANY.
|
||||||
# This option allows you to restrict the TCP address and provide
|
# This option allows you to restrict the TCP address and provide
|
||||||
# some degree of protection from the outside world.
|
# some degree of protection from the outside world.
|
||||||
# Default: disabled
|
# Default: disabled
|
||||||
|
# Binding only to localhost prevents external hosts from submitting files to clamd.
|
||||||
TCPAddr localhost
|
TCPAddr localhost
|
||||||
|
|
||||||
# Maximum length the queue of pending connections may grow to.
|
# 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).
|
# Deprecated option to alert on encrypted archives and documents (encrypted .zip, .7zip, .rar, .pdf).
|
||||||
# Default: no
|
# Default: no
|
||||||
#ArchiveBlockEncrypted 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
|
# Default: no
|
||||||
LogTime yes
|
LogTime yes
|
||||||
|
|
||||||
# Log additional information about the infected file, such as its
|
# Include file size and MD5/SHA hashes in threat reports — useful for
|
||||||
# size and hash, together with the virus name.
|
# submitting false-positive or false-negative reports to ClamAV.
|
||||||
ExtendedDetectionInfo yes
|
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
|
# Default: don't drop privileges
|
||||||
User clamav
|
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
|
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,
|
# Set the mount point where to recursively perform the scan,
|
||||||
# this could be every path or multiple path (one line for path)
|
# this could be every path or multiple path (one line for path)
|
||||||
OnAccessMountPath /
|
OnAccessMountPath /
|
||||||
|
|
@ -788,41 +800,59 @@ OnAccessMountPath /
|
||||||
# It works with OnAccessIncludePath, as long as /usr and /etc are not included.
|
# It works with OnAccessIncludePath, as long as /usr and /etc are not included.
|
||||||
# Including /var while activating prevention is also not recommended, because
|
# Including /var while activating prevention is also not recommended, because
|
||||||
# this would slow down package installation by a factor of 1000.
|
# 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
|
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
|
# Perform scans on newly created, moved, or renamed files
|
||||||
OnAccessExtraScanning yes
|
OnAccessExtraScanning yes
|
||||||
|
|
||||||
# Optionallyexclude root-owned processes
|
# Optionallyexclude root-owned processes
|
||||||
# OnAccessExcludeRootUID true
|
# 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.
|
# Maximum depth directories are scanned at.
|
||||||
# Default: 15
|
# Default: 15
|
||||||
MaxDirectoryRecursion 200
|
MaxDirectoryRecursion 200
|
||||||
|
|
||||||
|
# TCP socket commented out here — the active TCPSocket/TCPAddr declarations
|
||||||
|
# appear earlier in this file (generated section).
|
||||||
#TCPAddr localhost
|
#TCPAddr localhost
|
||||||
#TCPSocket 3310
|
#TCPSocket 3310
|
||||||
|
|
||||||
DetectPUA yes
|
# ── Enable all major scan categories ───────────────────────────────────────────
|
||||||
HeuristicAlerts yes
|
# Each directive below enables a specific scanner module. All default to "yes"
|
||||||
ScanPE yes
|
# in recent ClamAV, but are listed explicitly to document intent and prevent
|
||||||
ScanELF yes
|
# a future default change from silently disabling a scanner.
|
||||||
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
|
|
||||||
|
|
||||||
|
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
|
VirusEvent /etc/clamav/virus-event.bash
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,49 @@
|
||||||
#!/bin/bash
|
#!/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
|
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
|
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
|
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
|
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
|
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-clamonacc.service
|
||||||
sudo systemctl enable clamav-daemon.service
|
sudo systemctl enable clamav-daemon.service
|
||||||
sudo systemctl enable clamav-freshclam.service
|
sudo systemctl enable clamav-freshclam.service
|
||||||
sudo systemctl enable clamav-freshclam-once.timer
|
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
|
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
|
reboot
|
||||||
|
|
|
||||||
28
colors.conf
28
colors.conf
|
|
@ -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
|
# Edit values, then run: ~/Dotfiles/apply-theme.sh
|
||||||
# Bare 6-digit hex values only (no # prefix)
|
# Bare 6-digit hex values only (no # prefix)
|
||||||
|
|
||||||
COLOR_TEXT=D6ABAB # Quasi-White Text — foreground, labels
|
COLOR_TEXT=D6ABAB # Quasi-White Text — foreground, labels; slightly warm/muted to reduce eye strain
|
||||||
COLOR_BG=1A1A1A # Gray Background — base surface
|
COLOR_BG=1A1A1A # Gray Background — near-black base surface; dark enough to make colors pop
|
||||||
COLOR_HIGHLIGHT=E40046 # Hot Pink Accent — primary accent, active borders
|
COLOR_HIGHLIGHT=E40046 # Hot Pink Accent — primary accent used for active borders, selections, and highlights
|
||||||
COLOR_DARK=5018DD # Electric Violet — secondary accent, inactive borders
|
COLOR_DARK=5018DD # Electric Violet — secondary accent used for inactive borders and informational segments
|
||||||
COLOR_RED=F50505 # Red Hi-vis — danger, alerts
|
COLOR_RED=F50505 # Red Hi-vis — danger color for errors, alerts, and high-visibility UI elements
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,20 @@ usage() {
|
||||||
URL="$1"
|
URL="$1"
|
||||||
CUSTOM_NAME="${2:-}"
|
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"
|
[[ "$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?://[^/]+')
|
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\.)?||')
|
DOMAIN=$(echo "$BASE_URL" | sed -E 's|https?://(www\.)?||')
|
||||||
|
|
||||||
echo "Fetching: $URL"
|
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 \
|
PAGE_HTML=$(curl -sL --max-time 15 \
|
||||||
-A "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" \
|
-A "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" \
|
||||||
"$URL" 2>/dev/null || echo "")
|
"$URL" 2>/dev/null || echo "")
|
||||||
|
|
@ -32,6 +39,8 @@ PAGE_HTML=$(curl -sL --max-time 15 \
|
||||||
if [[ -n "$CUSTOM_NAME" ]]; then
|
if [[ -n "$CUSTOM_NAME" ]]; then
|
||||||
APP_NAME="$CUSTOM_NAME"
|
APP_NAME="$CUSTOM_NAME"
|
||||||
else
|
else
|
||||||
|
# Python inline: parse the <title> tag and HTML-unescape entities like &
|
||||||
|
# so the .desktop Name field looks clean. Falls back to DOMAIN on failure.
|
||||||
APP_NAME=$(echo "$PAGE_HTML" | python3 -c '
|
APP_NAME=$(echo "$PAGE_HTML" | python3 -c '
|
||||||
import sys, re, html as html_mod
|
import sys, re, html as html_mod
|
||||||
content = sys.stdin.read()
|
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}"
|
APP_NAME="${APP_NAME:-$DOMAIN}"
|
||||||
fi
|
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/-*$//')
|
SAFE_ID=$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-' | sed 's/^-*//;s/-*$//')
|
||||||
[[ -z "$SAFE_ID" ]] && SAFE_ID="$DOMAIN"
|
[[ -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 '
|
FAVICON_URL=$(echo "$PAGE_HTML" | python3 -c '
|
||||||
import sys, re
|
import sys, re
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
@ -76,24 +92,31 @@ if candidates:
|
||||||
print(urljoin(base, candidates[0][1]), end="")
|
print(urljoin(base, candidates[0][1]), end="")
|
||||||
' "$BASE_URL" 2>/dev/null || true)
|
' "$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}"
|
FAVICON_URL="${FAVICON_URL:-$BASE_URL/favicon.ico}"
|
||||||
echo "Favicon: $FAVICON_URL"
|
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"
|
ICON_DIR="$HOME/.local/share/icons/webapps"
|
||||||
mkdir -p "$ICON_DIR"
|
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)
|
TMP=$(mktemp /tmp/webapp-icon.XXXXXX)
|
||||||
trap 'rm -f "$TMP"' EXIT
|
trap 'rm -f "$TMP"' EXIT
|
||||||
|
|
||||||
|
# Default: fall through to the chromium themed icon if download/convert fails.
|
||||||
ICON_PATH="chromium" # fallback
|
ICON_PATH="chromium" # fallback
|
||||||
|
|
||||||
if curl -sL --max-time 10 -A "Mozilla/5.0" -o "$TMP" "$FAVICON_URL" && [[ -s "$TMP" ]]; then
|
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 \
|
if command -v convert &>/dev/null \
|
||||||
&& convert "${TMP}[0]" -resize 128x128\> "${ICON_DIR}/${SAFE_ID}.png" 2>/dev/null; then
|
&& convert "${TMP}[0]" -resize 128x128\> "${ICON_DIR}/${SAFE_ID}.png" 2>/dev/null; then
|
||||||
ICON_PATH="${ICON_DIR}/${SAFE_ID}.png"
|
ICON_PATH="${ICON_DIR}/${SAFE_ID}.png"
|
||||||
echo "Icon: $ICON_PATH (PNG via ImageMagick)"
|
echo "Icon: $ICON_PATH (PNG via ImageMagick)"
|
||||||
else
|
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")
|
MIME=$(file -b --mime-type "$TMP")
|
||||||
EXT="${MIME##*/}"
|
EXT="${MIME##*/}"
|
||||||
[[ "$EXT" == "x-icon" || "$EXT" == "vnd.microsoft.icon" ]] && EXT="ico"
|
[[ "$EXT" == "x-icon" || "$EXT" == "vnd.microsoft.icon" ]] && EXT="ico"
|
||||||
|
|
@ -105,7 +128,9 @@ else
|
||||||
echo "Warning: could not fetch favicon — using chromium default icon"
|
echo "Warning: could not fetch favicon — using chromium default icon"
|
||||||
fi
|
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"
|
DESKTOP_DIR="$HOME/.local/share/applications"
|
||||||
mkdir -p "$DESKTOP_DIR"
|
mkdir -p "$DESKTOP_DIR"
|
||||||
DESKTOP_FILE="${DESKTOP_DIR}/webapp-${SAFE_ID}.desktop"
|
DESKTOP_FILE="${DESKTOP_DIR}/webapp-${SAFE_ID}.desktop"
|
||||||
|
|
@ -122,6 +147,7 @@ Categories=Network;WebBrowser;
|
||||||
StartupWMClass=${SAFE_ID}
|
StartupWMClass=${SAFE_ID}
|
||||||
DESKTOP
|
DESKTOP
|
||||||
|
|
||||||
|
# Mark executable so the file manager and XDG launchers treat it as launchable.
|
||||||
chmod +x "$DESKTOP_FILE"
|
chmod +x "$DESKTOP_FILE"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
#!/usr/bin/env bash
|
#!/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
|
echo $1 | openssl aes-256-cbc -d -a -pbkdf2 -pass pass:$2
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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 {
|
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.
|
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.
|
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.
|
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 {
|
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
|
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
|
on-timeout = loginctl lock-session # lock screen when timeout has passed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── listener 2: suspend after 10 minutes of inactivity ─────────────────────
|
||||||
listener {
|
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
|
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
|
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 {
|
#listener {
|
||||||
# timeout = 18000 #5h
|
# timeout = 18000 #5h
|
||||||
# on-timeout = /usr/bin/reboot #reboot
|
# on-timeout = /usr/bin/reboot #reboot
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,31 @@
|
||||||
source = ~/.config/input.conf
|
# ============================================================================
|
||||||
source = ~/.config/monitors.conf
|
# hyprland.conf — Main Hyprland compositor configuration
|
||||||
source = ~/.config/envvars.conf
|
#
|
||||||
source = ~/.config/binds.conf
|
# This is the entry point for the Hyprland window manager config. It sources
|
||||||
source = ~/.config/windowrules.conf
|
# all per-user split configs (input, monitors, env vars, keybinds, etc.) from
|
||||||
source = ~/.config/autostart.conf
|
# ~/.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 {
|
#plugin {
|
||||||
# hyprexpo {
|
# hyprexpo {
|
||||||
# columns = 3
|
# columns = 3
|
||||||
|
|
@ -21,9 +43,12 @@ source = ~/.config/autostart.conf
|
||||||
|
|
||||||
# Eample per-device config
|
# Eample per-device config
|
||||||
# See https://wiki.hyprland.org/Configuring/Keywords/#per-device-input-configs for more
|
# 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 {
|
device {
|
||||||
name = epic-mouse-v1
|
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/
|
# See https://wiki.hyprland.org/Configuring/Keywords/
|
||||||
# Set programs that you use
|
# Set programs that you use
|
||||||
$terminal = kitty
|
# These $variables are referenced in keybindings (binds.conf) so changing the
|
||||||
$fileManager = kitty -e yazi
|
# program here automatically updates every keybind that uses it.
|
||||||
$editor = kitty micro
|
$terminal = kitty # Primary terminal emulator
|
||||||
$menu = wofi --show=drun
|
$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.)
|
# 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/
|
# Refer to https://wiki.hyprland.org/Configuring/Variables/
|
||||||
|
|
||||||
|
# ── general: tiling gaps, borders, and layout ──────────────────────────────
|
||||||
# https://wiki.hyprland.org/Configuring/Variables/#general
|
# 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_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
|
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
|
border_size = 4
|
||||||
|
|
||||||
# https://wiki.hyprland.org/Configuring/Variables/#variable-types for info about colors
|
# 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(fc0588) 40deg
|
||||||
col.active_border = rgb(E40046) rgb(f50505) rgb(E40046) rgb(f50505) rgb(E40046) 35deg
|
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)
|
col.inactive_border = rgb(5018dd)
|
||||||
|
|
||||||
# Set to true enable resizing windows by clicking and dragging on borders and gaps
|
# 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
|
# 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
|
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
|
layout = dwindle
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── group: window grouping (tabbed windows) ─────────────────────────────────
|
||||||
|
# Groups let multiple windows share the same screen tile, switchable via a tab bar.
|
||||||
group {
|
group {
|
||||||
|
|
||||||
col.border_active = rgb(E40046)
|
# Border colours for grouped windows mirror the main active/inactive palette.
|
||||||
col.border_inactive = rgb(5018dd)
|
col.border_active = rgb(E40046) # Hot pink/red — focused group border
|
||||||
col.border_locked_active = rgb(f50505)
|
col.border_inactive = rgb(5018dd) # Electric blue — unfocused group border
|
||||||
col.border_locked_inactive = rgb(5018dd)
|
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 {
|
groupbar {
|
||||||
|
# font_family: Agave NerdFont provides the Nerd Font icon glyphs used
|
||||||
|
# throughout the system (bar, lock screen, etc.).
|
||||||
font_family = Agave NerdFont
|
font_family = Agave NerdFont
|
||||||
|
# font_size: 20pt readable at 2× HiDPI scaling without being too large.
|
||||||
font_size = 20
|
font_size = 20
|
||||||
|
# height: total height of the groupbar strip in pixels.
|
||||||
height = 25
|
height = 25
|
||||||
|
# round_only_edges = false means all corners are rounded, not just outer.
|
||||||
round_only_edges = false
|
round_only_edges = false
|
||||||
|
# indicator_height: height of the coloured active-tab underline indicator.
|
||||||
indicator_height = 25
|
indicator_height = 25
|
||||||
|
# stacked = false: tabs displayed horizontally side-by-side (not vertically).
|
||||||
stacked = false
|
stacked = false
|
||||||
|
|
||||||
|
# text_color: tab label colour — primary accent red.
|
||||||
text_color = rgb(E40046)
|
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
|
priority = 3
|
||||||
|
|
||||||
|
# rounding: corner radius for the tab bar itself — pill-shaped appearance.
|
||||||
rounding = 13
|
rounding = 13
|
||||||
|
# Active tab background — red accent to show which tab is selected.
|
||||||
col.active = rgb(E40046)
|
col.active = rgb(E40046)
|
||||||
|
# Inactive tab backgrounds — blue to distinguish non-selected tabs.
|
||||||
col.inactive = rgb(5018dd)
|
col.inactive = rgb(5018dd)
|
||||||
|
|
||||||
|
# Locked group tab colours (same as unlocked for visual consistency).
|
||||||
col.locked_active = rgb(E40046)
|
col.locked_active = rgb(E40046)
|
||||||
col.locked_inactive = rgb(5018dd)
|
col.locked_inactive = rgb(5018dd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── decoration: window rounding, transparency, and blur ─────────────────────
|
||||||
# https://wiki.hyprland.org/Configuring/Variables/#decoration
|
# https://wiki.hyprland.org/Configuring/Variables/#decoration
|
||||||
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
|
rounding = 20
|
||||||
|
|
||||||
# Change transparency of focused and unfocused windows
|
# 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
|
active_opacity = 1
|
||||||
|
# inactive_opacity: unfocused windows are 80% opaque so the blurred
|
||||||
|
# wallpaper shows through slightly, creating depth perception.
|
||||||
inactive_opacity = 0.8
|
inactive_opacity = 0.8
|
||||||
|
# Drop shadow disabled: coloured borders already provide sufficient depth cues.
|
||||||
#drop_shadow = true
|
#drop_shadow = true
|
||||||
#shadow_range = 4
|
#shadow_range = 4
|
||||||
#shadow_render_power = 3
|
#shadow_render_power = 3
|
||||||
#col.shadow = rgba(1a1a1aee)
|
#col.shadow = rgba(1a1a1aee)
|
||||||
|
|
||||||
# https://wiki.hyprland.org/Configuring/Variables/#blur
|
# https://wiki.hyprland.org/Configuring/Variables/#blur
|
||||||
|
# blur: Gaussian-style background blur behind transparent/inactive windows.
|
||||||
blur {
|
blur {
|
||||||
enabled = true
|
enabled = true
|
||||||
|
# size: blur kernel radius — 3 is subtle and not GPU-heavy.
|
||||||
size = 3
|
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
|
passes = 3
|
||||||
|
|
||||||
|
# vibrancy: boosts colour saturation of the blurred background content.
|
||||||
|
# 0.1696 ≈ 17% vibrancy — subtle colour pop behind transparent windows.
|
||||||
vibrancy = 0.1696
|
vibrancy = 0.1696
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── animations ──────────────────────────────────────────────────────────────
|
||||||
# https://wiki.hyprland.org/Configuring/Variables/#animations
|
# https://wiki.hyprland.org/Configuring/Variables/#animations
|
||||||
animations {
|
animations {
|
||||||
enabled = true
|
enabled = true
|
||||||
|
|
||||||
# Default animations, see https://wiki.hyprland.org/Configuring/Animations/ for more
|
# 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
|
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
|
animation = windows, 1, 7, myBezier
|
||||||
|
# Window close: "popin" effect shrinks the window from 80% size on close.
|
||||||
animation = windowsOut, 1, 7, default, popin 80%
|
animation = windowsOut, 1, 7, default, popin 80%
|
||||||
|
# Border colour transition (gradient rotation): default easing, speed 10.
|
||||||
animation = border, 1, 10, default
|
animation = border, 1, 10, default
|
||||||
|
# Border angle animation (rotating gradient sweep): speed 8.
|
||||||
animation = borderangle, 1, 8, default
|
animation = borderangle, 1, 8, default
|
||||||
|
# Fade in/out for opacity transitions: speed 7.
|
||||||
animation = fade, 1, 7, default
|
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, 6, default
|
||||||
#animation = workspaces,1,5,default,slidevert
|
#animation = workspaces,1,5,default,slidevert
|
||||||
|
# Special workspace (scratchpad "magic"): slides in/out vertically at speed 10.
|
||||||
animation = specialWorkspace, 1, 10, default, slidevert
|
animation = specialWorkspace, 1, 10, default, slidevert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── dwindle layout ──────────────────────────────────────────────────────────
|
||||||
# See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more
|
# See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more
|
||||||
dwindle {
|
dwindle {
|
||||||
#pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
|
#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
|
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
|
special_scale_factor = 0.95
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── master layout ───────────────────────────────────────────────────────────
|
||||||
# See https://wiki.hyprland.org/Configuring/Master-Layout/ for more
|
# See https://wiki.hyprland.org/Configuring/Master-Layout/ for more
|
||||||
master {
|
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
|
new_status = master
|
||||||
|
# special_scale_factor: same 95% fill for the scratchpad in master mode.
|
||||||
special_scale_factor = 0.95
|
special_scale_factor = 0.95
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── misc ────────────────────────────────────────────────────────────────────
|
||||||
# https://wiki.hyprland.org/Configuring/Variables/#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
|
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. :(
|
disable_hyprland_logo = true # If true disables the random hyprland logo / anime girl background. :(
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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",
|
"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" ],
|
"modules-left": ["clock", "disk", "memory", "cpu", "temperature" ],
|
||||||
|
// Center: Hyprland workspace switcher + currently focused window title.
|
||||||
"modules-center": [ "hyprland/workspaces", "hyprland/window"],
|
"modules-center": [ "hyprland/workspaces", "hyprland/window"],
|
||||||
|
// Right: network icon, local IP, system tray, audio volume, battery.
|
||||||
"modules-right": [ "network", "custom/netaddrsimple", "tray", "pulseaudio", "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,
|
"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": {
|
"custom/netaddrsimple": {
|
||||||
|
// {} is substituted with the stdout of the exec command.
|
||||||
"format": "IP:{}",
|
"format": "IP:{}",
|
||||||
|
// No tooltip needed — the IP text in the bar is sufficient.
|
||||||
"tooltip":false,
|
"tooltip":false,
|
||||||
|
// (Commented out) would limit the displayed text to 15 characters.
|
||||||
//"max-length": 15,
|
//"max-length": 15,
|
||||||
|
// Refresh every 10 seconds — local IP rarely changes.
|
||||||
"interval": 10,
|
"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": {
|
"hyprland/workspaces": {
|
||||||
|
// Display the workspace name (typically its number) as label text.
|
||||||
"format": "{name}",
|
"format": "{name}",
|
||||||
|
// These icon overrides replace the label when a workspace matches
|
||||||
|
// the "default", "active", or "urgent" state keys.
|
||||||
"format-icons": {
|
"format-icons": {
|
||||||
|
// Unfocused, non-urgent workspaces get a plain bullet.
|
||||||
"default": " ",
|
"default": " ",
|
||||||
|
// The currently focused workspace is marked with "@".
|
||||||
"active": "@",
|
"active": "@",
|
||||||
|
// A workspace with an urgent app (needs attention) shows "!".
|
||||||
"urgent": "!"
|
"urgent": "!"
|
||||||
},
|
},
|
||||||
|
// Scroll wheel up → move to numerically higher workspace.
|
||||||
"on-scroll-up": "hyprctl dispatch workspace e+1",
|
"on-scroll-up": "hyprctl dispatch workspace e+1",
|
||||||
|
// Scroll wheel down → move to numerically lower workspace.
|
||||||
"on-scroll-down": "hyprctl dispatch workspace e-1",
|
"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": {
|
"clock": {
|
||||||
"format": "{:L%H:%M}",
|
// Locale-aware HH:MM format (24-hour).
|
||||||
|
"format": "{:L%H:%M}",
|
||||||
"tooltip": true,
|
"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>"
|
"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":{
|
"idle_inhibitor":{
|
||||||
|
// Wrap the icon in a larger font-size span so the glyph is readable.
|
||||||
"format": "<span font='12'>{icon} </span>",
|
"format": "<span font='12'>{icon} </span>",
|
||||||
"format-icons": {
|
"format-icons": {
|
||||||
|
// Nerd Font "eye open" — currently inhibiting idle / screen-off.
|
||||||
"activated":"",
|
"activated":"",
|
||||||
|
// Nerd Font "eye closed" — idle allowed normally.
|
||||||
"deactivated":""
|
"deactivated":""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ── clock ─────────────────────────────────────────────────
|
||||||
|
// Real-time HH:MM:SS clock shown in the left section.
|
||||||
|
// Updates every second so the seconds digit stays live.
|
||||||
"clock": {
|
"clock": {
|
||||||
|
// Show hours, minutes, and seconds with a trailing space.
|
||||||
"format": "{:%H:%M:%S }",
|
"format": "{:%H:%M:%S }",
|
||||||
|
// 1-second interval to keep the clock accurate to the second.
|
||||||
"interval":1,
|
"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>",
|
"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",
|
"calendar-weeks-pos": "right",
|
||||||
|
// Highlight today's date cell in the cyberqueer purple (#7645AD).
|
||||||
"today-format": "<span color='#7645AD'><b><u>{}</u></b></span>",
|
"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>",
|
"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>",
|
"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>"
|
"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": {
|
"bluetooth": {
|
||||||
"format-on": "",
|
// Bluetooth is on but nothing is connected.
|
||||||
|
"format-on": "",
|
||||||
|
// Bluetooth is turned off.
|
||||||
"format-off": "",
|
"format-off": "",
|
||||||
|
// Bluetooth hardware is disabled at the kernel/driver level.
|
||||||
"format-disabled": "",
|
"format-disabled": "",
|
||||||
|
// At least one device is connected.
|
||||||
"format-connected": "",
|
"format-connected": "",
|
||||||
|
// Connected device reports battery level — show it alongside the icon.
|
||||||
"format-connected-battery": "{device_battery_percentage}% ",
|
"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",
|
"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}",
|
"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}",
|
"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}%",
|
"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",
|
"on-click": "rofi-bluetooth",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ── battery ─────────────────────────────────────────────
|
||||||
|
// Shows remaining battery capacity and charging state.
|
||||||
|
// 1-second interval so the "PWR-" / "PWR+" prefix switches instantly.
|
||||||
"battery": {
|
"battery": {
|
||||||
"interval":1,
|
"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": {
|
"states": {
|
||||||
"good": 95,
|
"good": 95,
|
||||||
"warning": 30,
|
"warning": 30,
|
||||||
"critical": 20
|
"critical": 20
|
||||||
},
|
},
|
||||||
|
// Discharging: "PWR-" prefix + capacity + level icon.
|
||||||
"format": "PWR- {capacity}% {icon} ",
|
"format": "PWR- {capacity}% {icon} ",
|
||||||
|
// Charging via AC: "PWR+" prefix + the charging glyph.
|
||||||
"format-charging": "PWR+ {capacity}% ",
|
"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}",
|
"format-alt": "{time} {icon}",
|
||||||
|
// Six Nerd Font battery glyphs mapping roughly to 0→100% in steps.
|
||||||
"format-icons": [
|
"format-icons": [
|
||||||
"",
|
"", // 0–20 %
|
||||||
"",
|
"", // 20–40 %
|
||||||
"",
|
"", // 40–60 %
|
||||||
"",
|
"", // 60–80 %
|
||||||
"",
|
"", // 80–95 %
|
||||||
""
|
"" // 95–100 %
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ── backlight ──────────────────────────────────────────────
|
||||||
|
// Screen brightness via the intel_backlight sysfs interface.
|
||||||
|
// Scroll wheel on this module adjusts brightness by ±10 units.
|
||||||
"backlight": {
|
"backlight": {
|
||||||
|
// Target the Intel integrated GPU backlight node specifically.
|
||||||
"device": "intel_backlight",
|
"device": "intel_backlight",
|
||||||
|
// Display only the icon — brightness level communicated visually.
|
||||||
"format": "<span font='12'>{icon}</span>",
|
"format": "<span font='12'>{icon}</span>",
|
||||||
|
// Ten sun/brightness Nerd Font icons mapping 0%→100% in 10% steps.
|
||||||
"format-icons": [
|
"format-icons": [
|
||||||
"",
|
"", // 0–10 %
|
||||||
"",
|
"", // 10–20 %
|
||||||
"",
|
"", // 20–30 %
|
||||||
"",
|
"", // 30–40 %
|
||||||
"",
|
"", // 40–50 %
|
||||||
"",
|
"", // 50–60 %
|
||||||
"",
|
"", // 60–70 %
|
||||||
"",
|
"", // 70–80 %
|
||||||
"",
|
"", // 80–90 %
|
||||||
"",
|
"", // 90–100 %
|
||||||
],
|
],
|
||||||
|
// Scroll down decreases brightness (note: flag names are swapped in `light`).
|
||||||
"on-scroll-down": "light -A 10",
|
"on-scroll-down": "light -A 10",
|
||||||
|
// Scroll up increases brightness.
|
||||||
"on-scroll-up": "light -U 10",
|
"on-scroll-up": "light -U 10",
|
||||||
|
// Threshold of 1 means no acceleration — every scroll tick = 10 units.
|
||||||
"smooth-scrolling-threshold": 1
|
"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": {
|
"disk": {
|
||||||
"interval": 30,
|
"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": "/"
|
"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": {
|
"custom/colorpicker": {
|
||||||
|
// {} is replaced with the JSON "text" field from the script.
|
||||||
"format": "{}",
|
"format": "{}",
|
||||||
|
// Script returns {text, tooltip} JSON — waybar parses both fields.
|
||||||
"return-type": "json",
|
"return-type": "json",
|
||||||
|
// "once" means only run at startup; RTMIN+1 triggers re-execution.
|
||||||
"interval": "once",
|
"interval": "once",
|
||||||
|
// On load: render the most-recently-picked color as a dot.
|
||||||
"exec": "~/.config/waybar/scripts/colorpicker.sh -j",
|
"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",
|
"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
|
"signal": 1
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ── cpu ──────────────────────────────────────────────────
|
||||||
|
// Aggregate CPU usage across all cores, updated every second.
|
||||||
"cpu": {
|
"cpu": {
|
||||||
"interval": 1,
|
"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,
|
"min-length": 6,
|
||||||
"max-length": 6,
|
"max-length": 6,
|
||||||
|
// Bar-graph Unicode chars available for use in custom format strings.
|
||||||
"format-icons": ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"],
|
"format-icons": ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ── memory ────────────────────────────────────────────────
|
||||||
|
// RAM usage as a percentage. Compact single-field display.
|
||||||
"memory": {
|
"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": {
|
"hyprland/window": {
|
||||||
|
// Wrap the class name in parentheses for a "terminal prompt" aesthetic.
|
||||||
"format": "( {class} )",
|
"format": "( {class} )",
|
||||||
"rewrite": {
|
"rewrite": {
|
||||||
|
// Strip " - Mozilla Firefox" from page titles; prepend a globe.
|
||||||
"(.*) - Mozilla Firefox": "🌎 $1",
|
"(.*) - Mozilla Firefox": "🌎 $1",
|
||||||
|
// Kitty/foot terminal: show the current directory in brackets.
|
||||||
"(.*) - zsh": "> [$1]"
|
"(.*) - 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": {
|
"temperature": {
|
||||||
"format": " {temperatureC}°C",
|
// Normal state: thermometer glyph + temperature in Celsius.
|
||||||
"format-critical": " {temperatureC}°C",
|
"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,
|
"interval": 1,
|
||||||
|
// Above 80 °C the module switches to the "critical" CSS class.
|
||||||
"critical-threshold": 80,
|
"critical-threshold": 80,
|
||||||
|
// Left-click opens btop in the foot Wayland terminal.
|
||||||
"on-click": "foot btop",
|
"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": {
|
"pulseaudio": {
|
||||||
|
// Default format: percentage + device-type icon.
|
||||||
"format": "{volume}% {icon}",
|
"format": "{volume}% {icon}",
|
||||||
|
// When the active sink is a Bluetooth device, show a BT-speaker glyph.
|
||||||
"format-bluetooth":"",
|
"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": {
|
"format-icons": {
|
||||||
"headphones": "",
|
"headphones": "",
|
||||||
"bluetooth": "",
|
"bluetooth": "",
|
||||||
"handsfree": "",
|
"handsfree": "",
|
||||||
"headset": "",
|
"headset": "",
|
||||||
"phone": "",
|
"phone": "",
|
||||||
"portable": "",
|
"portable": "",
|
||||||
"car": "",
|
"car": "",
|
||||||
|
// Default (built-in speaker): three-tier glyphs for low/mid/high volume.
|
||||||
"default": ["","",""]
|
"default": ["","",""]
|
||||||
},
|
},
|
||||||
|
// Center-align the label within the module's allocated width.
|
||||||
"justify": "center",
|
"justify": "center",
|
||||||
|
// Toggle Master channel mute via amixer on left-click.
|
||||||
"on-click": "amixer sset Master toggle",
|
"on-click": "amixer sset Master toggle",
|
||||||
|
// Open the graphical PulseAudio volume control on right-click.
|
||||||
"on-click-right": "pavucontrol",
|
"on-click-right": "pavucontrol",
|
||||||
|
// Tooltip shows icon + volume % for a quick glance.
|
||||||
"tooltip-format": "{icon} {volume}%"
|
"tooltip-format": "{icon} {volume}%"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ── jack ─────────────────────────────────────────────────
|
||||||
|
// JACK Audio Connection Kit DSP load monitor.
|
||||||
|
// Relevant when running pro-audio software that uses JACK directly.
|
||||||
"jack": {
|
"jack": {
|
||||||
|
// {} = current DSP percentage (how busy the JACK graph is).
|
||||||
"format": "{} ",
|
"format": "{} ",
|
||||||
|
// Xruns are buffer underruns — audio glitches. Non-zero = problem.
|
||||||
"format-xrun": "{xruns} xruns",
|
"format-xrun": "{xruns} xruns",
|
||||||
|
// JACK server is not running.
|
||||||
"format-disconnected": "DSP off",
|
"format-disconnected": "DSP off",
|
||||||
|
// Poll in real-time (rather than on an interval) for accuracy.
|
||||||
"realtime": true
|
"realtime": true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ── tray ────────────────────────────────────────────────
|
||||||
|
// System tray using the StatusNotifierItem / libdbusmenu protocol.
|
||||||
|
// Apps like NetworkManager applet, Blueman, etc. dock here.
|
||||||
"tray": {
|
"tray": {
|
||||||
|
// Icon render size in pixels — 14 px is compact but readable.
|
||||||
"icon-size": 14,
|
"icon-size": 14,
|
||||||
|
// Horizontal gap between tray icons in pixels.
|
||||||
"spacing": 10
|
"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": {
|
"upower": {
|
||||||
|
// Don't show a persistent icon in the bar.
|
||||||
"show-icon": false,
|
"show-icon": false,
|
||||||
|
// Collapse the module entirely when no UPower devices are present.
|
||||||
"hide-if-empty": true,
|
"hide-if-empty": true,
|
||||||
"tooltip": true,
|
"tooltip": true,
|
||||||
|
// Vertical spacing between device entries in the tooltip.
|
||||||
"tooltip-spacing": 20
|
"tooltip-spacing": 20
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// ── network ─────────────────────────────────────────────
|
||||||
|
// Connection-type indicator: one icon for wifi, one for ethernet,
|
||||||
|
// one for disconnected. Hovering reveals SSID, signal, or IP.
|
||||||
"network":{
|
"network":{
|
||||||
"format-wifi": " ",
|
// Wireless: show only a wifi Nerd Font glyph (no text).
|
||||||
"format-ethernet":" ",
|
"format-wifi": " ",
|
||||||
"format-disconnected": "",
|
// 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": "{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}"
|
"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": {
|
"custom/powerDraw": {
|
||||||
|
// {} replaced by the JSON "text" field from the script output.
|
||||||
"format": "{}",
|
"format": "{}",
|
||||||
|
// Poll every second — power draw is volatile.
|
||||||
"interval": 1,
|
"interval": 1,
|
||||||
|
// Script reads /sys/class/power_supply/BAT*/power_now and formats JSON.
|
||||||
"exec": "~/.config/waybar/scripts/powerdraw.sh",
|
"exec": "~/.config/waybar/scripts/powerdraw.sh",
|
||||||
|
// JSON return type lets the script provide both text and tooltip.
|
||||||
"return-type": "json"
|
"return-type": "json"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
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-family: Agave Nerd Font Mono, sans-serif;
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
|
|
||||||
|
/* Transparent default so color only appears where explicitly set. */
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|
||||||
|
/* Compact vertical padding keeps the bar slim. */
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
padding-bottom: 2px;
|
padding-bottom: 2px;
|
||||||
|
/* Horizontal padding gives each module text a bit of breathing room. */
|
||||||
padding-right: 6px;
|
padding-right: 6px;
|
||||||
padding-left: 6px;
|
padding-left: 6px;
|
||||||
|
|
||||||
|
/* 1 px top margin keeps module pills off the very top pixel. */
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
|
/* 2 px horizontal margin creates the small gap between adjacent modules. */
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
|
|
||||||
|
/* 30 px radius turns every element into a rounded pill shape. */
|
||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Workspace buttons — inactive state ───────────────────────
|
||||||
|
* Each workspace number is a GTK button.
|
||||||
|
* Inactive (non-focused) workspaces: dark background + purple text.
|
||||||
|
*/
|
||||||
#workspaces button {
|
#workspaces button {
|
||||||
|
/* 3 px solid border traces the pill outline clearly. */
|
||||||
border: solid;
|
border: solid;
|
||||||
border-width: 3px;
|
border-width: 3px;
|
||||||
|
/* Near-black background matches the general panel color. */
|
||||||
background: #1a1a1a;
|
background: #1a1a1a;
|
||||||
|
/* Electric purple for inactive workspace labels. */
|
||||||
color: #5018dd;
|
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 {
|
#workspaces button.active {
|
||||||
border: solid;
|
border: solid;
|
||||||
border-width: 3px;
|
border-width: 3px;
|
||||||
background: #1a1a1a;
|
background: #1a1a1a;
|
||||||
|
/* Neon accent color makes the active workspace unmistakable. */
|
||||||
color: #E40046;
|
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 {
|
#workspaces button.urgent {
|
||||||
border: solid;
|
border: solid;
|
||||||
border-width: 3px;
|
border-width: 3px;
|
||||||
|
/* Flipped: background becomes the neon accent. */
|
||||||
background: #E40046;
|
background: #E40046;
|
||||||
|
/* Dark text on bright background for maximum contrast. */
|
||||||
color: #1a1a1a;
|
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 {
|
#clock, #disk, #window, #battery, #cpu, #memory, #temperature, #backlight, #network, #pulseaudio, #custom-media, #tray, #mode, #idle_inhibitor, #custom-netaddrsimple {
|
||||||
border: solid;
|
border: solid;
|
||||||
border-width: 3px;
|
border-width: 3px;
|
||||||
|
/* Dark panel background shared across all status modules. */
|
||||||
background: #1a1a1a;
|
background: #1a1a1a;
|
||||||
|
/* Electric purple for all default module text and Nerd Font icons. */
|
||||||
color: #5018dd;
|
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 {
|
#idle_inhibitor.activated {
|
||||||
border: solid;
|
border: solid;
|
||||||
border-width: 3px;
|
border-width: 3px;
|
||||||
background: #1a1a1a;
|
background: #1a1a1a;
|
||||||
|
/* Neon red-pink = "something is actively overriding idle". */
|
||||||
color: #E40046;
|
color: #E40046;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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]
|
(defwindow bar [monitor]
|
||||||
:monitor monitor
|
:monitor monitor
|
||||||
:class "ewwbar"
|
:class "ewwbar"
|
||||||
|
|
@ -9,55 +28,88 @@
|
||||||
:height "20px"
|
:height "20px"
|
||||||
:anchor "top center")
|
:anchor "top center")
|
||||||
:exclusive true
|
:exclusive true
|
||||||
|
; Render the bar widget, passing the monitor index through for workspace tracking
|
||||||
(bar :monitor_ monitor))
|
(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"
|
(defpoll battery :interval "2s"
|
||||||
"~/Dotfiles/desktopenvs/hyprland/scripts/batteryperc")
|
"~/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_]
|
(defwidget bar [monitor_]
|
||||||
(centerbox :orientation "h"
|
(centerbox :orientation "h"
|
||||||
(winsworks :monitor monitor_)
|
(winsworks :monitor monitor_)
|
||||||
(music)
|
(music)
|
||||||
(sidestuff)))
|
(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]
|
(defwidget winsworks [monitor]
|
||||||
(box :orientation "h" :space-evenly false :halign "start"
|
(box :orientation "h" :space-evenly false :halign "start"
|
||||||
|
; Battery percentage badge — styled as a pill with class "music"
|
||||||
(box :class "music" {"${battery}"})
|
(box :class "music" {"${battery}"})
|
||||||
|
; Workspace dots — one button per active workspace on this monitor
|
||||||
(workspaceWidget :monitor 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 []
|
(defwidget sidestuff []
|
||||||
(box :class "sidestuff" :orientation "h" :space-evenly false :halign "end"
|
(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 ""
|
(metric :label ""
|
||||||
:value volume
|
:value volume
|
||||||
:onchange "pactl set-sink-volume @DEFAULT_SINK@ {}%"
|
:onchange "pactl set-sink-volume @DEFAULT_SINK@ {}%"
|
||||||
:onclick "killall pavucontrol || hyprctl eval 'hl.dsp.exec_cmd(\"[tag +mixer] pavucontrol\")'")
|
: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}
|
:tooltip {disks}
|
||||||
(metric :label ""
|
(metric :label ""
|
||||||
:value {round((1 - (EWW_DISK["/"].free / EWW_DISK["/"].total)) * 100, 0)}
|
:value {round((1 - (EWW_DISK["/"].free / EWW_DISK["/"].total)) * 100, 0)}
|
||||||
:onchange ""
|
:onchange ""
|
||||||
:onclick ""))
|
:onclick ""))
|
||||||
|
|
||||||
|
; Caffeine toggle button — shows ☕ when active, sleeping icon when off
|
||||||
(caffeine)
|
(caffeine)
|
||||||
|
; Clock widget with calendar tooltip
|
||||||
(clock)
|
(clock)
|
||||||
|
; System tray — renders applets from running applications (nm-applet, blueman, etc.)
|
||||||
(systray :class "music" :orientation "h" :spacing 2 :space-evenly true)
|
(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 []
|
(defwidget music []
|
||||||
(button :class "music"
|
(button :class "music"
|
||||||
:orientation "h"
|
:orientation "h"
|
||||||
:space-evenly false
|
:space-evenly false
|
||||||
:halign "center"
|
:halign "center"
|
||||||
:onclick "~/Dotfiles/desktopenvs/hyprland/scripts/playpause.sh"
|
: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]
|
(defwidget metric [label value onchange onclick]
|
||||||
(box :orientation "h"
|
(box :orientation "h"
|
||||||
:class "metric"
|
:class "metric"
|
||||||
|
|
@ -65,12 +117,21 @@
|
||||||
(button :class "label" :onclick onclick label)
|
(button :class "label" :onclick onclick label)
|
||||||
(scale :min 0
|
(scale :min 0
|
||||||
:max 101
|
:max 101
|
||||||
|
; Only make the slider interactive if an onchange command was provided
|
||||||
:active {onchange != ""}
|
:active {onchange != ""}
|
||||||
:value value
|
:value value
|
||||||
:onchange onchange)))
|
: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 _")
|
(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]
|
(defwidget workspaceWidget [monitor]
|
||||||
(eventbox :onscroll "hyprctl eval \"hl.dsp.focus({workspace='$(echo {} | sed 's/up/r+/;s/down/r-/')1'})\""
|
(eventbox :onscroll "hyprctl eval \"hl.dsp.focus({workspace='$(echo {} | sed 's/up/r+/;s/down/r-/')1'})\""
|
||||||
(box :class "workspaces"
|
(box :class "workspaces"
|
||||||
|
|
@ -79,51 +140,75 @@
|
||||||
(for i in {workspaces[monitor].workspaces}
|
(for i in {workspaces[monitor].workspaces}
|
||||||
(button
|
(button
|
||||||
:width 20
|
:width 20
|
||||||
|
; Click a workspace button to focus it via Lua dispatch
|
||||||
:onclick "hyprctl eval 'hl.dsp.focus({workspace=${i.id}})'"
|
:onclick "hyprctl eval 'hl.dsp.focus({workspace=${i.id}})'"
|
||||||
|
; i.class comes from hyprland-workspaces: "active", "occupied", or ""
|
||||||
:class "${i.class}"
|
: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")
|
(deflisten workspace-old "~/Dotfiles/desktopenvs/hyprland/scripts/workspace")
|
||||||
(defwidget workspaces-old []
|
(defwidget workspaces-old []
|
||||||
(literal :content workspace-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"
|
(defpoll music :interval "0.5s"
|
||||||
"~/Dotfiles/desktopenvs/hyprland/scripts/playerget")
|
"~/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"
|
(defpoll activewindow :interval "0.5s"
|
||||||
"~/Dotfiles/desktopenvs/hyprland/scripts/activewindow")
|
"~/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"
|
(defpoll IP :interval "5s"
|
||||||
"~/Dotfiles/desktopenvs/hyprland/scripts/ip")
|
"~/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"
|
(defpoll volume :interval "0.5s"
|
||||||
"~/Dotfiles/desktopenvs/hyprland/scripts/getvol")
|
"~/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"
|
(defpoll time :interval "1s"
|
||||||
"date '+%H:%M:%S|%d.%m.%Y'")
|
"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 []
|
(defwidget clock []
|
||||||
(box :class "clock"
|
(box :class "clock"
|
||||||
:tooltip {calender}
|
: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"
|
(defpoll calender :interval "600s"
|
||||||
"~/Dotfiles/desktopenvs/hyprland/scripts/calender-fix.sh")
|
"~/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"
|
(defpoll disks :interval "600s"
|
||||||
"~/Dotfiles/desktopenvs/hyprland/scripts/dysk-phydisks.sh")
|
"~/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"
|
(defpoll caffeine-active :interval "2s"
|
||||||
"~/Dotfiles/desktopenvs/hyprlua/scripts/caffeine-status.sh")
|
"~/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 []
|
(defwidget caffeine []
|
||||||
(button :class "music"
|
(button :class "music"
|
||||||
:onclick "~/Dotfiles/desktopenvs/hyprlua/scripts/caffeine.sh"
|
:onclick "~/Dotfiles/desktopenvs/hyprlua/scripts/caffeine.sh"
|
||||||
:tooltip {caffeine-active == "true" ? "Caffeine: ON" : "Caffeine: OFF"}
|
:tooltip {caffeine-active == "true" ? "Caffeine: ON" : "Caffeine: OFF"}
|
||||||
{caffeine-active == "true" ? "☕" : ""}))
|
{caffeine-active == "true" ? "☕" : ""}))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
# =============================================================================
|
||||||
|
|
@ -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 {
|
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_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
|
before_sleep_cmd = loginctl lock-session
|
||||||
|
|
||||||
# fprintd restart ensures fingerprint sensor is ready after resume
|
# 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
|
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)
|
ignore_dbus_inhibit = false # respect systemd-inhibit locks (presence-detect, caffeine)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Presence detection resets the idle timer every 2 minutes while you're visible,
|
# Presence detection resets the idle timer every 2 minutes while you're visible,
|
||||||
# so these timeouts only run when you've actually stepped away.
|
# 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 {
|
listener {
|
||||||
timeout = 150 # 2.5 min — lock screen
|
timeout = 150 # 2.5 min — lock screen
|
||||||
|
# loginctl lock-session triggers the session lock signal; hyprlock picks it up.
|
||||||
on-timeout = loginctl lock-session
|
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 {
|
listener {
|
||||||
timeout = 600 # 10 min — suspend
|
timeout = 600 # 10 min — suspend
|
||||||
on-timeout = systemctl suspend-then-hibernate
|
on-timeout = systemctl suspend-then-hibernate
|
||||||
|
|
|
||||||
|
|
@ -1,88 +1,174 @@
|
||||||
-- Hyprland Lua config — https://wiki.hypr.land/Configuring/Start/
|
-- =============================================================================
|
||||||
|
-- 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/).
|
-- Device-specific files live in ~/.config/hypr/usr/ (deployed from hypr/usr/).
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
require("monitors")
|
-- Pull in per-device configuration modules.
|
||||||
require("usr.envvars")
|
-- monitors is loaded from the Lua search path root (monitors.lua, managed by
|
||||||
require("usr.input")
|
-- hyprmoncfg); the remaining modules live in usr/ and are per-machine overrides.
|
||||||
require("usr.binds")
|
-- They are loaded in this specific order so that later modules can safely
|
||||||
require("usr.windowrules")
|
-- depend on earlier ones (e.g., binds may reference programs launched by
|
||||||
require("usr.autostart")
|
-- autostart; input-device-exceptions builds on the global input baseline).
|
||||||
|
require("monitors") -- monitor layout, resolution, scale (managed by hyprmoncfg)
|
||||||
|
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 ---
|
---- MY PROGRAMS ---
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
local terminal = "kitty"
|
-- Convenience variables naming the preferred application for each role.
|
||||||
local fileManager = "thunar"
|
-- These are referenced by keybind definitions in usr/binds.lua so that
|
||||||
local editor = "kitty nvim"
|
-- changing the preferred terminal, editor, etc. requires only a single edit here.
|
||||||
local menu = "vicinae toggle"
|
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 ----
|
---- 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({
|
hl.config({
|
||||||
general = {
|
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,
|
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,
|
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,
|
border_size = 4,
|
||||||
col = {
|
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 },
|
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)",
|
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,
|
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,
|
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",
|
layout = "dwindle",
|
||||||
},
|
},
|
||||||
group = {
|
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 = {
|
col = {
|
||||||
|
-- Active tab in a window group: accent red to match the active window border.
|
||||||
border_active = "rgb(E40046)",
|
border_active = "rgb(E40046)",
|
||||||
|
-- Inactive tabs fade to dark blue to de-emphasise them.
|
||||||
border_inactive = "rgb(5018dd)",
|
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_active = "rgb(f50505)",
|
||||||
border_locked_inactive = "rgb(5018dd)",
|
border_locked_inactive = "rgb(5018dd)",
|
||||||
},
|
},
|
||||||
groupbar = {
|
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_family = "Agave NerdFont",
|
||||||
font_size = 20,
|
font_size = 20, -- px; large enough to be readable at 1.5x HiDPI scale
|
||||||
height = 25,
|
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,
|
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,
|
indicator_height = 25,
|
||||||
|
-- stacked = false: tabs appear side-by-side horizontally (breadcrumb style),
|
||||||
|
-- not vertically stacked. Horizontal layout conserves vertical screen space.
|
||||||
stacked = false,
|
stacked = false,
|
||||||
|
-- Tab label text colour: accent red, consistent with the active border colour.
|
||||||
text_color = "rgb(E40046)",
|
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,
|
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,
|
rounding = 13,
|
||||||
col = {
|
col = {
|
||||||
|
-- Active tab fill: solid accent red, clearly selected.
|
||||||
active = "rgb(E40046)",
|
active = "rgb(E40046)",
|
||||||
|
-- Inactive tab fill: blue background recedes visually.
|
||||||
inactive = "rgb(5018dd)",
|
inactive = "rgb(5018dd)",
|
||||||
|
-- Locked-group tab colours mirror the border locked colours for consistency.
|
||||||
locked_active = "rgb(E40046)",
|
locked_active = "rgb(E40046)",
|
||||||
locked_inactive = "rgb(5018dd)",
|
locked_inactive = "rgb(5018dd)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
decoration = {
|
decoration = {
|
||||||
|
-- Window corner radius (px). 20 px gives a modern "card" look that matches
|
||||||
|
-- the GTK cyberqueer theme's rounded widgets.
|
||||||
rounding = 20,
|
rounding = 20,
|
||||||
|
-- Focused window: fully opaque (1.0) so content is crisp and readable.
|
||||||
active_opacity = 1,
|
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,
|
inactive_opacity = 0.8,
|
||||||
blur = {
|
blur = {
|
||||||
|
-- Gaussian blur applied behind transparent/translucent surfaces (terminals,
|
||||||
|
-- unfocused windows). This creates the "frosted glass" depth effect.
|
||||||
enabled = true,
|
enabled = true,
|
||||||
size = 3,
|
size = 3, -- blur kernel radius in pixels; small = tight, localised blur halo
|
||||||
passes = 3,
|
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,
|
vibrancy = 0.1696,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
animations = {
|
animations = {
|
||||||
|
-- Master switch for all compositor animations.
|
||||||
|
-- Individual animation types are configured below in the animations section.
|
||||||
enabled = true,
|
enabled = true,
|
||||||
},
|
},
|
||||||
dwindle = {
|
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,
|
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,
|
special_scale_factor = 0.95,
|
||||||
},
|
},
|
||||||
master = {
|
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",
|
new_status = "master",
|
||||||
|
-- Same 95% scratchpad scale as dwindle, for visual consistency.
|
||||||
special_scale_factor = 0.95,
|
special_scale_factor = 0.95,
|
||||||
},
|
},
|
||||||
misc = {
|
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,
|
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,
|
disable_hyprland_logo = true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -91,19 +177,44 @@ hl.config({
|
||||||
---- ANIMATIONS --
|
---- 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} } })
|
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" })
|
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%" })
|
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" })
|
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" })
|
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" })
|
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" })
|
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 10, bezier = "default", style = "slidevert" })
|
||||||
|
|
||||||
--------------
|
--------------
|
||||||
---- DEVICE ---
|
---- 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({
|
hl.device({
|
||||||
name = "epic-mouse-v1",
|
name = "epic-mouse-v1",
|
||||||
sensitivity = -0.5,
|
sensitivity = -0.5,
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,24 @@
|
||||||
|
|
||||||
|
|
||||||
general {
|
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
|
grace = 2
|
||||||
|
|
||||||
hide_cursor = true
|
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
|
ignore_empty_input = true
|
||||||
}
|
}
|
||||||
|
|
||||||
auth {
|
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
|
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
|
fingerprint:enabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -16,20 +27,37 @@ auth {
|
||||||
source = ~/.config/hypr/hyprlock-backgrounds.conf
|
source = ~/.config/hypr/hyprlock-backgrounds.conf
|
||||||
|
|
||||||
input-field {
|
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 =
|
monitor =
|
||||||
|
|
||||||
|
# Width × height in pixels of the password box.
|
||||||
size = 300, 60
|
size = 300, 60
|
||||||
|
|
||||||
|
# Pixel thickness of the coloured outline drawn around the input box.
|
||||||
outline_thickness = 4
|
outline_thickness = 4
|
||||||
|
|
||||||
|
# Dot diameter as a fraction of the input-field height (0.0–1.0).
|
||||||
|
# 0.5 = dots are half the field height, giving a balanced look.
|
||||||
dots_size = 0.5
|
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_spacing = 0.2
|
||||||
|
|
||||||
dots_center = true
|
dots_center = true
|
||||||
hide_input = false
|
hide_input = false
|
||||||
|
|
||||||
|
# Pango markup is supported; <i> renders italic placeholder text.
|
||||||
placeholder_text = <i>Password...</i>
|
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%
|
position = 0, 20%
|
||||||
halign = center
|
halign = center
|
||||||
valign = bottom
|
valign = bottom
|
||||||
|
|
||||||
# Accent border & color matching EWW
|
# Accent border & color matching EWW
|
||||||
|
# Dark near-black fill so text/dots are readable against any wallpaper.
|
||||||
inner_color = rgb(1a1a1a)
|
inner_color = rgb(1a1a1a)
|
||||||
font_color = rgb(b0b4bc)
|
font_color = rgb(b0b4bc)
|
||||||
font_family = Agave Nerd Font Mono
|
font_family = Agave Nerd Font Mono
|
||||||
|
|
@ -37,10 +65,14 @@ input-field {
|
||||||
|
|
||||||
# DATE
|
# DATE
|
||||||
label {
|
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
|
text = cmd[update:18000000] sh ~/Dotfiles/desktopenvs/hyprland/scripts/date.sh
|
||||||
color = rgb(5018dd)
|
color = rgb(5018dd)
|
||||||
font_size = 34
|
font_size = 34
|
||||||
font_family = Agave Nerd Font Mono
|
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%
|
position = 0, 40%
|
||||||
halign = center
|
halign = center
|
||||||
valign = center
|
valign = center
|
||||||
|
|
@ -48,10 +80,13 @@ label {
|
||||||
|
|
||||||
# TIME
|
# TIME
|
||||||
label {
|
label {
|
||||||
|
# update:1000 = refresh every second so the clock ticks in real time.
|
||||||
text = cmd[update:1000] sh ~/Dotfiles/desktopenvs/hyprland/scripts/time.sh
|
text = cmd[update:1000] sh ~/Dotfiles/desktopenvs/hyprland/scripts/time.sh
|
||||||
color = rgb(E40046)
|
color = rgb(E40046)
|
||||||
|
# Large font makes the time the dominant visual element on the lock screen.
|
||||||
font_size = 95
|
font_size = 95
|
||||||
font_family = Agave Nerd Font Mono
|
font_family = Agave Nerd Font Mono
|
||||||
|
# 25% above centre keeps the time above the input field while staying mid-screen.
|
||||||
position = 0, 25%
|
position = 0, 25%
|
||||||
halign = center
|
halign = center
|
||||||
valign = center
|
valign = center
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
#!/bin/bash
|
#!/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
|
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"
|
"$(dirname "$(readlink -f "$0")")/ewwstart.sh"
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,19 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Max character count before the title is truncated with an ellipsis.
|
||||||
trunc=16
|
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}')
|
sample=$(hyprctl activewindow | grep title: | awk -F: '{print $2}')
|
||||||
|
|
||||||
#echo ${sample}
|
#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
|
if [ ${#sample} -gt $trunc ]; then
|
||||||
echo $sample | head -c $trunc | sed 's/$/…/'
|
echo $sample | head -c $trunc | sed 's/$/…/'
|
||||||
else
|
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
|
if [ ${#sample} -ne 0 ]; then
|
||||||
echo $sample
|
echo $sample
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
#!/bin/bash
|
#!/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}')
|
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}')
|
state=$(upower -i /org/freedesktop/UPower/devices/battery_BAT1 | awk '/state/ {print $2}')
|
||||||
|
|
||||||
# Check if values are not empty
|
# Check if values are not empty
|
||||||
|
|
@ -10,12 +11,14 @@ if [ -z "$perc" ] || [ -z "$state" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Convert to integer
|
# Strip any fractional part for integer comparisons below (upower may return "42.00").
|
||||||
num=${perc%%.*} # In case perc is float
|
num=${perc%%.*} # In case perc is float
|
||||||
|
|
||||||
|
# Show a plug/charging icon regardless of level while the charger is connected.
|
||||||
if [ "$state" == "charging" ]; then
|
if [ "$state" == "charging" ]; then
|
||||||
echo " ${perc}%"
|
echo " ${perc}%"
|
||||||
else
|
else
|
||||||
|
# Map the level to a Nerd Font battery icon — 10% steps, critical at ≤10%.
|
||||||
if [ "$num" -gt 95 ]; then
|
if [ "$num" -gt 95 ]; then
|
||||||
echo " ${perc}%"
|
echo " ${perc}%"
|
||||||
elif [ "$num" -gt 90 ]; then
|
elif [ "$num" -gt 90 ]; then
|
||||||
|
|
@ -37,6 +40,7 @@ else
|
||||||
elif [ "$num" -gt 10 ]; then
|
elif [ "$num" -gt 10 ]; then
|
||||||
echo " ${perc}%"
|
echo " ${perc}%"
|
||||||
else
|
else
|
||||||
|
# At ≤10% fire a critical urgency desktop notification via dunst/libnotify.
|
||||||
notify-send --urgency=critical -t 2000 " low battery, please charge"
|
notify-send --urgency=critical -t 2000 " low battery, please charge"
|
||||||
echo " ${perc}%"
|
echo " ${perc}%"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,5 @@
|
||||||
#!/bin/bash
|
#!/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
|
hyprctl dispatch exec blueman-applet
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
#!/bin/bash
|
#!/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"
|
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"
|
[[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null && echo "true" || echo "false"
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,25 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Toggle idle inhibit via systemd-inhibit (hypridle respects the logind idle hint).
|
# 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"
|
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
|
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")"
|
kill "$(cat "$PID_FILE")"
|
||||||
rm -f "$PID_FILE"
|
rm -f "$PID_FILE"
|
||||||
notify-send -t 2000 "Caffeine" "Idle inhibit OFF"
|
notify-send -t 2000 "Caffeine" "Idle inhibit OFF"
|
||||||
else
|
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 \
|
systemd-inhibit --what=idle \
|
||||||
--who="caffeine" \
|
--who="caffeine" \
|
||||||
--why="Caffeine mode active" \
|
--why="Caffeine mode active" \
|
||||||
--mode=block \
|
--mode=block \
|
||||||
sleep infinity &
|
sleep infinity &
|
||||||
|
# $! is the PID of the most recently backgrounded process (sleep infinity above).
|
||||||
echo $! > "$PID_FILE"
|
echo $! > "$PID_FILE"
|
||||||
notify-send -t 2000 "Caffeine" "Idle inhibited"
|
notify-send -t 2000 "Caffeine" "Idle inhibited"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# %-d strips the leading zero so "06" becomes "6" — needed for bracket matching below.
|
||||||
today=$(date +%-d)
|
today=$(date +%-d)
|
||||||
|
# %u is ISO weekday: 1=Monday … 7=Sunday.
|
||||||
weekdaynum=$(date +%u)
|
weekdaynum=$(date +%u)
|
||||||
weekday=""
|
weekday=""
|
||||||
|
# Map ISO weekday number to the two-letter abbreviation that `cal` prints in its header.
|
||||||
if [[ $weekdaynum -eq 1 ]]; then
|
if [[ $weekdaynum -eq 1 ]]; then
|
||||||
weekday="Mo"
|
weekday="Mo"
|
||||||
elif [[ $weekdaynum -eq 2 ]]; then
|
elif [[ $weekdaynum -eq 2 ]]; then
|
||||||
|
|
@ -15,12 +18,15 @@ elif [[ $weekdaynum -eq 5 ]]; then
|
||||||
elif [[ $weekdaynum -eq 6 ]]; then
|
elif [[ $weekdaynum -eq 6 ]]; then
|
||||||
weekday="Sa"
|
weekday="Sa"
|
||||||
elif [[ $weekdaynum -eq 7 ]]; then
|
elif [[ $weekdaynum -eq 7 ]]; then
|
||||||
weekday="Su"
|
weekday="Su"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ======================
|
echo ======================
|
||||||
date '+%A, %d.%m.%Y'
|
date '+%A, %d.%m.%Y'
|
||||||
echo ======================
|
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]/"
|
cal -m | sed -e 's/^/ /' | sed "s/ $today /[$today]/" | sed "s/ $weekday /[$weekday]/"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,4 @@
|
||||||
#!/bin/bash
|
#!/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>"
|
echo "<b> "$(date +'%A, %-d %B %Y')" </b>"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,9 @@
|
||||||
#!/bin/bash
|
#!/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
|
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
#!/bin/bash
|
#!/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')
|
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'
|
echo "$rawdiskstr" | sed '$d'
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,18 @@
|
||||||
#!/bin/bash
|
#!/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
|
/usr/bin/eww daemon
|
||||||
|
|
||||||
GTK_THEME=cyberqueer
|
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)
|
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--
|
monitorsum--
|
||||||
for i in $(seq 1 $monitorsum);
|
for i in $(seq 1 $monitorsum);
|
||||||
do
|
do
|
||||||
|
# Convert 1-based loop index to 0-based monitor ID.
|
||||||
declare -i curmon=$i-1
|
declare -i curmon=$i-1
|
||||||
/usr/bin/eww open bar --id bar$curmon --arg monitor=$curmon
|
/usr/bin/eww open bar --id bar$curmon --arg monitor=$curmon
|
||||||
done
|
done
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,20 @@
|
||||||
#!/bin/bash
|
#!/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
|
killall eww
|
||||||
|
# Start the Eww daemon in the background; subsequent `eww open` calls connect to it.
|
||||||
/usr/bin/eww daemon
|
/usr/bin/eww daemon
|
||||||
|
# GTK_THEME is read by GTK3 widgets embedded in Eww (used for theme override).
|
||||||
GTK_THEME=cyberqueer
|
GTK_THEME=cyberqueer
|
||||||
|
# Count connected monitors via hyprctl — each "ID" line corresponds to one monitor.
|
||||||
monitorsum=$(hyprctl monitors | grep ID | wc -l)
|
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);
|
for i in $(seq 1 $monitorsum);
|
||||||
do
|
do
|
||||||
|
# $i is 1-based (seq 1 N) but monitor IDs are 0-based, so subtract 1.
|
||||||
declare -i curmon=$i-1
|
declare -i curmon=$i-1
|
||||||
/usr/bin/eww open bar --id bar$curmon --arg monitor=$curmon
|
/usr/bin/eww open bar --id bar$curmon --arg monitor=$curmon
|
||||||
done
|
done
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,5 @@
|
||||||
#!/bin/bash
|
#!/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)"
|
kitty --directory="$(cat ~/.z | cut -d"|" -f1 | wofi --show=dmenu)"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
#!/bin/bash
|
#!/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)
|
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"
|
notify-send -t 200000 "$speedvar"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
#!/bin/sh
|
#!/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)
|
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})
|
num=$(echo ${perc::-1})
|
||||||
echo $num
|
echo $num
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
#!/bin/bash
|
#!/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
|
cat ~/Dotfiles/desktopenvs/hyprlua/hypr/usr/binds.lua | less
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
#!/usr/bin/env bash
|
#!/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"
|
export STATUS_FILE="$XDG_RUNTIME_DIR/keyboard.status"
|
||||||
|
|
||||||
enable_keyboard() {
|
enable_keyboard() {
|
||||||
printf "true" >"$STATUS_FILE"
|
printf "true" >"$STATUS_FILE"
|
||||||
notify-send -u normal "Enabling Touchpad"
|
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"
|
hyprctl keyword 'device[synaptics-tm3053-009]:enabled' "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -14,9 +18,11 @@ disable_keyboard() {
|
||||||
hyprctl keyword 'device[synaptics-tm3053-009]:enabled' "false"
|
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
|
if ! [ -f "$STATUS_FILE" ]; then
|
||||||
enable_keyboard
|
enable_keyboard
|
||||||
else
|
else
|
||||||
|
# Toggle based on the persisted state: true → disable, false → enable.
|
||||||
if [ $(cat "$STATUS_FILE") = "true" ]; then
|
if [ $(cat "$STATUS_FILE") = "true" ]; then
|
||||||
disable_keyboard
|
disable_keyboard
|
||||||
elif [ $(cat "$STATUS_FILE") = "false" ]; then
|
elif [ $(cat "$STATUS_FILE") = "false" ]; then
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
#!/bin/bash
|
#!/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
|
hostname -i | tr ' ' '\n' | head -n1 | tail -n1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
#!/bin/bash
|
#!/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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,7 @@
|
||||||
#!/bin/bash
|
#!/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"
|
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"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
#!/bin/bash
|
#!/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
|
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
|
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
|
fi
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,26 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Separate truncation limits for song title vs artist name (song gets more space).
|
||||||
truncs=13
|
truncs=13
|
||||||
trunca=10
|
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 }}')
|
song=$(playerctl metadata --format '{{ title }}')
|
||||||
artist=$(playerctl metadata --format '{{ artist }}')
|
artist=$(playerctl metadata --format '{{ artist }}')
|
||||||
#echo ${sample}
|
#echo ${sample}
|
||||||
|
|
||||||
|
# Truncate song title to truncs chars and append "…" if it exceeds the limit.
|
||||||
if [ ${#song} -gt $truncs ]; then
|
if [ ${#song} -gt $truncs ]; then
|
||||||
songt=$(echo $song | head -c $truncs | sed 's/$/…/')
|
songt=$(echo $song | head -c $truncs | sed 's/$/…/')
|
||||||
else
|
else
|
||||||
if [ ${#song} -ne 0 ]; then
|
if [ ${#song} -ne 0 ]; then
|
||||||
songt=$(echo ${song})
|
songt=$(echo ${song})
|
||||||
else
|
else
|
||||||
|
# No media playing — use "None" so the Eww widget always has content.
|
||||||
songt=$(echo None)
|
songt=$(echo None)
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Same truncation logic for the artist field with a shorter limit.
|
||||||
if [ ${#artist} -gt $trunca ]; then
|
if [ ${#artist} -gt $trunca ]; then
|
||||||
artistt=$(echo $artist | head -c $trunca | sed 's/$/…/')
|
artistt=$(echo $artist | head -c $trunca | sed 's/$/…/')
|
||||||
else
|
else
|
||||||
|
|
@ -24,6 +30,7 @@ else
|
||||||
artistt=$(echo None)
|
artistt=$(echo None)
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
# Output as "song|artist" — the Eww widget splits on "|" to show them separately.
|
||||||
echo "${songt}|${artistt}"
|
echo "${songt}|${artistt}"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,5 @@
|
||||||
#!/bin/bash
|
#!/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
|
playerctl play-pause -p spotify, vlc, firefox
|
||||||
|
|
|
||||||
|
|
@ -8,50 +8,63 @@
|
||||||
#
|
#
|
||||||
# Exit codes from python helper: 0=face, 1=no face, 2=camera error
|
# 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)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
PYTHON_DETECT="$SCRIPT_DIR/python/presence_detect.py"
|
PYTHON_DETECT="$SCRIPT_DIR/python/presence_detect.py"
|
||||||
INHIBIT_PID_FILE="/tmp/presence-inhibit.pid"
|
INHIBIT_PID_FILE="/tmp/presence-inhibit.pid"
|
||||||
PRESENCE_CFG="${XDG_CONFIG_HOME:-$HOME/.config}/presence-detect.conf"
|
PRESENCE_CFG="${XDG_CONFIG_HOME:-$HOME/.config}/presence-detect.conf"
|
||||||
INTERVAL=120 # seconds between checks
|
INTERVAL=120 # seconds between checks
|
||||||
|
|
||||||
|
# Resolve camera ID: env var takes highest priority, then config file, then default 0.
|
||||||
_camera_id() {
|
_camera_id() {
|
||||||
if [[ -n "$PRESENCE_DETECT_CAMERA" ]]; then
|
if [[ -n "$PRESENCE_DETECT_CAMERA" ]]; then
|
||||||
echo "$PRESENCE_DETECT_CAMERA"
|
echo "$PRESENCE_DETECT_CAMERA"
|
||||||
elif [[ -f "$PRESENCE_CFG" ]]; then
|
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
|
grep -oP 'CAMERA=\K[0-9]+' "$PRESENCE_CFG" 2>/dev/null || echo 0
|
||||||
else
|
else
|
||||||
echo 0
|
echo 0
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Returns true if the inhibitor sentinel process is still alive.
|
||||||
_inhibit_running() {
|
_inhibit_running() {
|
||||||
[[ -f "$INHIBIT_PID_FILE" ]] && kill -0 "$(cat "$INHIBIT_PID_FILE")" 2>/dev/null
|
[[ -f "$INHIBIT_PID_FILE" ]] && kill -0 "$(cat "$INHIBIT_PID_FILE")" 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
_start_inhibit() {
|
_start_inhibit() {
|
||||||
|
# Guard: don't start a second inhibitor if one is already active.
|
||||||
_inhibit_running && return
|
_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" \
|
systemd-inhibit --what=idle --who="presence-detect" \
|
||||||
--why="User presence detected" --mode=block \
|
--why="User presence detected" --mode=block \
|
||||||
sleep infinity &
|
sleep infinity &
|
||||||
echo $! > "$INHIBIT_PID_FILE"
|
echo $! > "$INHIBIT_PID_FILE"
|
||||||
|
# logger writes to the system journal — visible via `journalctl -t presence-detect`.
|
||||||
logger -t presence-detect "Presence detected — idle inhibited"
|
logger -t presence-detect "Presence detected — idle inhibited"
|
||||||
}
|
}
|
||||||
|
|
||||||
_stop_inhibit() {
|
_stop_inhibit() {
|
||||||
_inhibit_running || return
|
_inhibit_running || return
|
||||||
|
# Killing the sleep process releases the systemd-inhibit lock automatically.
|
||||||
kill "$(cat "$INHIBIT_PID_FILE")" 2>/dev/null
|
kill "$(cat "$INHIBIT_PID_FILE")" 2>/dev/null
|
||||||
rm -f "$INHIBIT_PID_FILE"
|
rm -f "$INHIBIT_PID_FILE"
|
||||||
logger -t presence-detect "No presence — idle inhibit released"
|
logger -t presence-detect "No presence — idle inhibit released"
|
||||||
}
|
}
|
||||||
|
|
||||||
_cleanup() {
|
_cleanup() {
|
||||||
|
# On daemon stop (systemd unit stop, user logout, etc.), release the lock cleanly.
|
||||||
_stop_inhibit
|
_stop_inhibit
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
# Intercept termination signals to ensure the inhibitor PID is never orphaned.
|
||||||
trap _cleanup SIGTERM SIGINT SIGHUP
|
trap _cleanup SIGTERM SIGINT SIGHUP
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
CAMERA="$(_camera_id)"
|
CAMERA="$(_camera_id)"
|
||||||
|
# Run the OpenCV haar-cascade detector; stderr suppressed to keep the journal clean.
|
||||||
python3 "$PYTHON_DETECT" "$CAMERA" 2>/dev/null
|
python3 "$PYTHON_DETECT" "$CAMERA" 2>/dev/null
|
||||||
rc=$?
|
rc=$?
|
||||||
case $rc in
|
case $rc in
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
#!/bin/zsh
|
#!/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
|
cat ~/.config/scripts/frequentcommands.list | wofi --show=dmenu | zsh
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
#!/bin/bash
|
#!/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}')
|
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
|
if [ "$state" = "[on] "]; then
|
||||||
amixer
|
amixer
|
||||||
# amixer -D pulse sset Master "$state" > /dev/null
|
# amixer -D pulse sset Master "$state" > /dev/null
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Returns 0 if $1 is a command available on PATH; used to guard optional tools.
|
||||||
check() {
|
check() {
|
||||||
command -v "$1" 1>/dev/null
|
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() {
|
notify() {
|
||||||
check notify-send && {
|
check notify-send && {
|
||||||
notify-send -a "Color Picker" "$@"
|
notify-send -a "Color Picker" "$@"
|
||||||
|
|
@ -12,49 +15,66 @@ notify() {
|
||||||
echo "$@"
|
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"
|
loc="$HOME/.cache/colorpicker"
|
||||||
[ -d "$loc" ] || mkdir -p "$loc"
|
[ -d "$loc" ] || mkdir -p "$loc"
|
||||||
[ -f "$loc/colors" ] || touch "$loc/colors"
|
[ -f "$loc/colors" ] || touch "$loc/colors"
|
||||||
|
|
||||||
|
# Maximum number of recent colours kept in the history file.
|
||||||
limit=10
|
limit=10
|
||||||
|
|
||||||
|
# -l mode: dump the raw colour list to stdout (consumed by other scripts or widgets).
|
||||||
[[ $# -eq 1 && $1 = "-l" ]] && {
|
[[ $# -eq 1 && $1 = "-l" ]] && {
|
||||||
cat "$loc/colors"
|
cat "$loc/colors"
|
||||||
exit
|
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" ]] && {
|
[[ $# -eq 1 && $1 = "-j" ]] && {
|
||||||
|
# The most-recently picked colour is always stored on the first line.
|
||||||
text="$(head -n 1 "$loc/colors")"
|
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")
|
mapfile -t allcolors < <(tail -n +2 "$loc/colors")
|
||||||
# allcolors=($(tail -n +2 "$loc/colors"))
|
# allcolors=($(tail -n +2 "$loc/colors"))
|
||||||
tooltip="<b> COLORS</b>\n\n"
|
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
|
for i in "${allcolors[@]}"; do
|
||||||
tooltip+=" <b>$i</b> <span color='$i'></span> \n"
|
tooltip+=" <b>$i</b> <span color='$i'></span> \n"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Waybar expects a single-line JSON object on stdout.
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
{ "text":"<span color='$text'></span>", "tooltip":"$tooltip"}
|
{ "text":"<span color='$text'></span>", "tooltip":"$tooltip"}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
exit
|
exit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Guard: hyprpicker must be installed — it is the Wayland screen colour-picker tool.
|
||||||
check hyprpicker || {
|
check hyprpicker || {
|
||||||
notify "hyprpicker is not installed"
|
notify "hyprpicker is not installed"
|
||||||
exit
|
exit
|
||||||
}
|
}
|
||||||
|
# Kill any stale hyprpicker instance before launching a new one; -q suppresses "no process" errors.
|
||||||
killall -q hyprpicker
|
killall -q hyprpicker
|
||||||
color=$(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 && {
|
check wl-copy && {
|
||||||
echo "$color" | sed -z 's/\n//g' | 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")
|
prevColors=$(head -n $((limit - 1)) "$loc/colors")
|
||||||
echo "$color" >"$loc/colors"
|
echo "$color" >"$loc/colors"
|
||||||
echo "$prevColors" >>"$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"
|
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
|
pkill -RTMIN+1 waybar
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# format: print '-' when the count is 0 so the output is readable rather than "0".
|
||||||
format() {
|
format() {
|
||||||
if [ "$1" -eq 0 ]; then
|
if [ "$1" -eq 0 ]; then
|
||||||
echo '-'
|
echo '-'
|
||||||
|
|
@ -7,18 +9,26 @@ format() {
|
||||||
fi
|
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
|
if ! updates_arch="$(checkupdates | wc -l)"; then
|
||||||
updates_arch=0
|
updates_arch=0
|
||||||
fi
|
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
|
if ! updates_aur="$(yay -Qum 2>/dev/null | wc -l)"; then
|
||||||
updates_aur=0
|
updates_aur=0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Total pending updates across both official repos and the AUR.
|
||||||
updates="$((updates_arch + updates_aur))"
|
updates="$((updates_arch + updates_aur))"
|
||||||
|
|
||||||
if [ "$updates" -gt 0 ]; then
|
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
|
else
|
||||||
|
# Print nothing when fully up-to-date (Waybar will show an empty text field).
|
||||||
echo
|
echo
|
||||||
fia
|
fi
|
||||||
|
# NOTE: original file contained a typo "fia" instead of "fi"; corrected above.
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,29 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Prefer AUR helpers over plain pacman when they are on PATH.
|
||||||
|
# hash is faster than which for simple existence checks.
|
||||||
pkgmgr="pacman"
|
pkgmgr="pacman"
|
||||||
hash paru 2>/dev/null && pkgmgr="paru"
|
hash paru 2>/dev/null && pkgmgr="paru"
|
||||||
hash yay 2>/dev/null && pkgmgr="yay"
|
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'
|
IFS=$'\n'$'\r'
|
||||||
|
|
||||||
|
# -Qu lists packages that have an available upgrade; array length = update count.
|
||||||
updatesli=($($pkgmgr -Qu))
|
updatesli=($($pkgmgr -Qu))
|
||||||
text=${#updatesli[@]}
|
text=${#updatesli[@]}
|
||||||
|
# Show an empty icon when up-to-date; show a package emoji when updates are pending.
|
||||||
icon=""
|
icon=""
|
||||||
[ $text -eq 0 ] && icon="" || icon="📦"
|
[ $text -eq 0 ] && icon="" || icon="📦"
|
||||||
|
|
||||||
|
# Build the tooltip string by concatenating each pending package on its own line.
|
||||||
for i in ${updatesli[@]}
|
for i in ${updatesli[@]}
|
||||||
do
|
do
|
||||||
tooltip+="$i\n"
|
tooltip+="$i\n"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Emit a Waybar JSON payload: text is the status icon, tooltip shows the count.
|
||||||
cat << EOF
|
cat << EOF
|
||||||
{ "text":"$icon", "tooltip":"UPDATES: $text"}
|
{ "text":"$icon", "tooltip":"UPDATES: $text"}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
#!/bin/bash
|
#!/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
|
if [ -f /sys/class/power_supply/BAT*/power_now ]; then
|
||||||
powerDraw=" $(($(cat /sys/class/power_supply/BAT*/power_now)/1000000))w"
|
powerDraw=" $(($(cat /sys/class/power_supply/BAT*/power_now)/1000000))w"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Emit a Waybar JSON payload; text and tooltip both show the wattage (or empty string on desktop).
|
||||||
cat << EOF
|
cat << EOF
|
||||||
{ "text":"$powerDraw", "tooltip":"power Draw $powerDraw"}
|
{ "text":"$powerDraw", "tooltip":"power Draw $powerDraw"}
|
||||||
EOF
|
EOF
|
||||||
|
|
|
||||||
|
|
@ -1,117 +1,169 @@
|
||||||
#!/bin/bash
|
#!/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
|
LOCATION=0
|
||||||
QRCODE_LOCATION=$LOCATION
|
QRCODE_LOCATION=$LOCATION
|
||||||
Y_AXIS=0
|
Y_AXIS=0
|
||||||
X_AXIS=0
|
X_AXIS=0
|
||||||
|
# NOTIFICATIONS_INIT: set to "on" in the conf file to enable desktop notifications.
|
||||||
NOTIFICATIONS_INIT="off"
|
NOTIFICATIONS_INIT="off"
|
||||||
|
# Directory where QR-code PNGs are cached.
|
||||||
QRCODE_DIR="/tmp/"
|
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_MAIN=1
|
||||||
WIDTH_FIX_STATUS=10
|
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)"
|
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."
|
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=($(nmcli device | awk '$2=="wifi" {print $1}'))
|
||||||
WIRELESS_INTERFACES_PRODUCT=()
|
WIRELESS_INTERFACES_PRODUCT=()
|
||||||
|
# WLAN_INT: index of the currently active Wi-Fi interface when multiple cards are present.
|
||||||
WLAN_INT=0
|
WLAN_INT=0
|
||||||
WIRED_INTERFACES=($(nmcli device | awk '$2=="ethernet" {print $1}'))
|
WIRED_INTERFACES=($(nmcli device | awk '$2=="ethernet" {print $1}'))
|
||||||
WIRED_INTERFACES_PRODUCT=()
|
WIRED_INTERFACES_PRODUCT=()
|
||||||
|
|
||||||
function initialization() {
|
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"
|
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
|
{ [[ -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 "${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
|
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
|
wireless_interface_state && ethernet_interface_state
|
||||||
}
|
}
|
||||||
function notification() {
|
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"
|
[[ "$NOTIFICATIONS_INIT" == "on" && -x "$(command -v notify-send)" ]] && notify-send -r "5" -u "normal" $1 "$2"
|
||||||
}
|
}
|
||||||
function wireless_interface_state() {
|
function wireless_interface_state() {
|
||||||
|
# No-op when no Wi-Fi cards were found at startup.
|
||||||
[[ ${#WIRELESS_INTERFACES[@]} -eq "0" ]] || {
|
[[ ${#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}')
|
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}')
|
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" ]] && {
|
{ [[ "$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]}]
|
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}')
|
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"
|
[[ "$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"
|
OPTIONS="${WIFI_LIST}\n${WIFI_SWITCH}\n"
|
||||||
}; }
|
}; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function ethernet_interface_state() {
|
function ethernet_interface_state() {
|
||||||
|
# No-op when no wired interfaces were found.
|
||||||
[[ ${#WIRED_INTERFACES[@]} -eq "0" ]] || {
|
[[ ${#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}')
|
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***"; }
|
{ [[ "$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"
|
OPTIONS="${OPTIONS}${WIRED_SWITCH}\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function wofi_menu() {
|
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"; }
|
{ [[ ${#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]}]"
|
{ [[ "$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")
|
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}')
|
SSID=$(echo "$SELECTION" | sed "s/\s\{2,\}/\|/g" | awk -F "|" '{print $1}')
|
||||||
selection_action
|
selection_action
|
||||||
}
|
}
|
||||||
function wofi_cmd() {
|
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)); }
|
{ [[ -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"''
|
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() {
|
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; }; } || {
|
{ [[ ${#WIRELESS_INTERFACES[@]} -eq "2" ]] && { [[ $WLAN_INT -eq "0" ]] && WLAN_INT=1 || WLAN_INT=0; }; } || {
|
||||||
LIST_WLAN_INT=""
|
LIST_WLAN_INT=""
|
||||||
for i in "${!WIRELESS_INTERFACES[@]}"; do LIST_WLAN_INT=("${LIST_WLAN_INT[@]}${WIRELESS_INTERFACES_PRODUCT[$i]}[${WIRELESS_INTERFACES[$i]}]\n"); done
|
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}
|
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)
|
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
|
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
|
wireless_interface_state && ethernet_interface_state
|
||||||
wofi_menu
|
wofi_menu
|
||||||
}
|
}
|
||||||
function scan() {
|
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
|
[[ "$WIFI_CON_STATE" =~ "unavailable" ]] && change_wifi_state "Wi-Fi" "Enabling Wi-Fi connection" "on" && sleep 2
|
||||||
notification "-t 0 Wifi" "Please Wait Scanning"
|
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}')
|
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
|
wireless_interface_state && ethernet_interface_state
|
||||||
notification "-t 1 Wifi" "Please Wait Scanning"
|
notification "-t 1 Wifi" "Please Wait Scanning"
|
||||||
wofi_menu
|
wofi_menu
|
||||||
}
|
}
|
||||||
function change_wifi_state() {
|
function change_wifi_state() {
|
||||||
|
# Toggle the Wi-Fi radio via nmcli: "on" enables it, "off" disables it.
|
||||||
notification "$1" "$2"
|
notification "$1" "$2"
|
||||||
nmcli radio wifi "$3"
|
nmcli radio wifi "$3"
|
||||||
}
|
}
|
||||||
function change_wired_state() {
|
function change_wired_state() {
|
||||||
|
# Connect or disconnect a wired interface: nmcli device connect/disconnect <iface>.
|
||||||
notification "$1" "$2"
|
notification "$1" "$2"
|
||||||
nmcli device "$3" "$4"
|
nmcli device "$3" "$4"
|
||||||
}
|
}
|
||||||
function net_restart() {
|
function net_restart() {
|
||||||
|
# Hard-cycle the NetworkManager stack; 3 s sleep lets the kernel teardown complete.
|
||||||
notification "$1" "$2"
|
notification "$1" "$2"
|
||||||
nmcli networking off && sleep 3 && nmcli networking on
|
nmcli networking off && sleep 3 && nmcli networking on
|
||||||
}
|
}
|
||||||
function disconnect() {
|
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)
|
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'"
|
notification "$1" "You're now disconnected from Wi-Fi network '$ACTIVE_SSID'"
|
||||||
nmcli con down id "$ACTIVE_SSID"
|
nmcli con down id "$ACTIVE_SSID"
|
||||||
}
|
}
|
||||||
function check_wifi_connected() {
|
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"
|
[[ "$(nmcli device status | grep "^${WIRELESS_INTERFACES[WLAN_INT]}." | awk '{print $3}')" == "connected" ]] && disconnect "Connection_Terminated"
|
||||||
}
|
}
|
||||||
function connect() {
|
function connect() {
|
||||||
check_wifi_connected
|
check_wifi_connected
|
||||||
notification "-t 0 Wi-Fi" "Connecting to $1"
|
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"
|
{ [[ $(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() {
|
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")
|
PROMPT="Enter_Password" && PASS=$(echo "$PASSWORD_ENTER" | wofi_cmd "$PASSWORD_ENTER" 4 "-password")
|
||||||
}
|
}
|
||||||
function enter_ssid() {
|
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)
|
PROMPT="Enter_SSID" && SSID=$(wofi_cmd "" 40)
|
||||||
}
|
}
|
||||||
function stored_connection() {
|
function stored_connection() {
|
||||||
|
# Re-activate an already-known connection profile without supplying a password.
|
||||||
check_wifi_connected
|
check_wifi_connected
|
||||||
notification "-t 0 Wi-Fi" "Connecting to $1"
|
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"
|
{ [[ $(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() {
|
function ssid_manual() {
|
||||||
|
# Let the user type an SSID; if a password is entered use it, otherwise try stored credentials.
|
||||||
enter_ssid
|
enter_ssid
|
||||||
[[ -n $SSID ]] && {
|
[[ -n $SSID ]] && {
|
||||||
enter_passwword
|
enter_passwword
|
||||||
|
|
@ -119,10 +171,12 @@ function ssid_manual() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function ssid_hidden() {
|
function ssid_hidden() {
|
||||||
|
# Connect to a hidden SSID by creating or reusing an NM connection profile.
|
||||||
enter_ssid
|
enter_ssid
|
||||||
[[ -n $SSID ]] && {
|
[[ -n $SSID ]] && {
|
||||||
enter_passwword && check_wifi_connected
|
enter_passwword && check_wifi_connected
|
||||||
[[ -n "$PASS" ]] && [[ "$PASS" != "$PASSWORD_ENTER" ]] && {
|
[[ -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 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.key-mgmt wpa-psk
|
||||||
nmcli con modify "$SSID" wifi-sec.psk "$PASS"
|
nmcli con modify "$SSID" wifi-sec.psk "$PASS"
|
||||||
|
|
@ -132,59 +186,80 @@ function ssid_hidden() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function interface_status() {
|
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
|
local -n INTERFACES=$1 && local -n INTERFACES_PRODUCT=$2
|
||||||
for i in "${!INTERFACES[@]}"; do
|
for i in "${!INTERFACES[@]}"; do
|
||||||
CON_STATE=$(nmcli device status | grep "^${INTERFACES[$i]}." | awk '{print $3}')
|
CON_STATE=$(nmcli device status | grep "^${INTERFACES[$i]}." | awk '{print $3}')
|
||||||
INT_NAME=${INTERFACES_PRODUCT[$i]}[${INTERFACES[$i]}]
|
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^}"
|
[[ "$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}"
|
echo -e "${STATUS}"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
function status() {
|
function status() {
|
||||||
|
# Build a combined status view of all wired and wireless interfaces plus any active VPN.
|
||||||
OPTIONS=""
|
OPTIONS=""
|
||||||
[[ ${#WIRED_INTERFACES[@]} -ne "0" ]] && ETH_STATUS="$(interface_status WIRED_INTERFACES WIRED_INTERFACES_PRODUCT)" && OPTIONS="${OPTIONS}${ETH_STATUS}"
|
[[ ${#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}"; }
|
[[ ${#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')
|
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}')"
|
[[ -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];}"
|
echo -e "$OPTIONS" | wofi_cmd "$OPTIONS" $WIDTH_FIX_STATUS "" "mainbox{children:[listview];}"
|
||||||
}
|
}
|
||||||
function share_pass() {
|
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)
|
SSID=$(nmcli dev wifi show-password | grep -oP '(?<=SSID: ).*' | head -1)
|
||||||
PASSWORD=$(nmcli dev wifi show-password | grep -oP '(?<=Password: ).*' | head -1)
|
PASSWORD=$(nmcli dev wifi show-password | grep -oP '(?<=Password: ).*' | head -1)
|
||||||
OPTIONS="SSID: ${SSID}\nPassword: ${PASSWORD}"
|
OPTIONS="SSID: ${SSID}\nPassword: ${PASSWORD}"
|
||||||
|
# Offer QR-code generation only when qrencode is installed.
|
||||||
[[ -x "$(command -v qrencode)" ]] && OPTIONS="${OPTIONS}\n~QrCode"
|
[[ -x "$(command -v qrencode)" ]] && OPTIONS="${OPTIONS}\n~QrCode"
|
||||||
SELECTION=$(echo -e "$OPTIONS" | wofi_cmd "$OPTIONS" $WIDTH_FIX_STATUS "-a -1" "mainbox{children:[listview];}")
|
SELECTION=$(echo -e "$OPTIONS" | wofi_cmd "$OPTIONS" $WIDTH_FIX_STATUS "-a -1" "mainbox{children:[listview];}")
|
||||||
selection_action
|
selection_action
|
||||||
}
|
}
|
||||||
function gen_qrcode() {
|
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")
|
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"";;"
|
[[ -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;
|
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);}"
|
background-image:url(\"$QRCODE_DIR$SSID.png\",both);}"
|
||||||
}
|
}
|
||||||
function manual_hidden() {
|
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];}")
|
OPTIONS="~Manual\n~Hidden" && SELECTION=$(echo -e "$OPTIONS" | wofi_cmd "$OPTIONS" $WIDTH_FIX_STATUS "" "mainbox{children:[listview];}")
|
||||||
selection_action
|
selection_action
|
||||||
}
|
}
|
||||||
function vpn() {
|
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=$(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')"
|
[[ $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];}")
|
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"; } || {
|
[[ -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)
|
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"
|
{ [[ $(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() {
|
function more_options() {
|
||||||
|
# Secondary menu: share password (only when connected), status, restart, VPN, editor.
|
||||||
OPTIONS=""
|
OPTIONS=""
|
||||||
[[ "$WIFI_CON_STATE" == "connected" ]] && OPTIONS="~Share Wifi Password\n"
|
[[ "$WIFI_CON_STATE" == "connected" ]] && OPTIONS="~Share Wifi Password\n"
|
||||||
OPTIONS="${OPTIONS}~Status\n~Restart Network"
|
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"
|
[[ $(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"
|
[[ -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=$(echo -e "$OPTIONS" | wofi_cmd "$OPTIONS" "$WIDTH_FIX_STATUS" "" "mainbox {children:[listview];}")
|
||||||
selection_action
|
selection_action
|
||||||
}
|
}
|
||||||
function selection_action() {
|
function selection_action() {
|
||||||
|
# Central dispatcher: map each menu label to its handler function.
|
||||||
case "$SELECTION" in
|
case "$SELECTION" in
|
||||||
"~Disconnect") disconnect "Connection_Terminated" ;;
|
"~Disconnect") disconnect "Connection_Terminated" ;;
|
||||||
"~Scan") scan ;;
|
"~Scan") scan ;;
|
||||||
|
|
@ -197,6 +272,7 @@ function selection_action() {
|
||||||
"~Wi-Fi Off") change_wifi_state "Wi-Fi" "Disabling Wi-Fi connection" "off" ;;
|
"~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 Off") change_wired_state "Ethernet" "Disabling Wired connection" "disconnect" "${WIRED_INTERFACES}" ;;
|
||||||
"~Eth On") change_wired_state "Ethernet" "Enabling Wired connection" "connect" "${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***") ;;
|
"***Wi-Fi Disabled***") ;;
|
||||||
"***Wired Unavailable***") ;;
|
"***Wired Unavailable***") ;;
|
||||||
"***Wired Initializing***") ;;
|
"***Wired Initializing***") ;;
|
||||||
|
|
@ -207,8 +283,12 @@ function selection_action() {
|
||||||
"~Open Connection Editor") nm-connection-editor ;;
|
"~Open Connection Editor") nm-connection-editor ;;
|
||||||
"~VPN") vpn ;;
|
"~VPN") vpn ;;
|
||||||
*)
|
*)
|
||||||
|
# Default: treat the selection as a network from WIFI_LIST.
|
||||||
[[ -n "$SELECTION" ]] && [[ "$WIFI_LIST" =~ .*"$SELECTION".* ]] && {
|
[[ -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}')
|
[[ "$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]}"; } || {
|
{ [[ "$ACTIVE_SSID" == "$SSID" ]] && nmcli con up "$SSID" ifname "${WIRELESS_INTERFACES[WLAN_INT]}"; } || {
|
||||||
[[ "$SELECTION" =~ "WPA2" ]] || [[ "$SELECTION" =~ "WEP" ]] && enter_passwword
|
[[ "$SELECTION" =~ "WPA2" ]] || [[ "$SELECTION" =~ "WEP" ]] && enter_passwword
|
||||||
{ [[ -n "$PASS" ]] && [[ "$PASS" != "$PASSWORD_ENTER" ]] && connect "$SSID" "$PASS"; } || stored_connection "$SSID"
|
{ [[ -n "$PASS" ]] && [[ "$PASS" != "$PASSWORD_ENTER" ]] && connect "$SSID" "$PASS"; } || stored_connection "$SSID"
|
||||||
|
|
@ -218,6 +298,7 @@ function selection_action() {
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
function main() {
|
function main() {
|
||||||
|
# Entry point: load config and launch the menu.
|
||||||
initialization && wofi_menu
|
initialization && wofi_menu
|
||||||
}
|
}
|
||||||
main
|
main
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,39 @@
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
// ~/.config/niri/config.kdl
|
// ~/.config/niri/config.kdl
|
||||||
// Niri scrollable-tiling Wayland compositor — CyberQueer config
|
// 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"
|
// ── Module includes ───────────────────────────────────────────────────────────
|
||||||
include "modules/outputs.kdl"
|
// Each file below is merged verbatim into this config at parse time.
|
||||||
include "modules/layout.kdl"
|
// The order matters only when settings in later files override earlier ones.
|
||||||
include "modules/animations.kdl"
|
|
||||||
include "modules/environment.kdl"
|
|
||||||
include "modules/autostart.kdl"
|
|
||||||
include "modules/window-rules.kdl"
|
|
||||||
include "modules/binds.kdl"
|
|
||||||
|
|
||||||
|
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
|
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"
|
screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
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 {
|
window-open {
|
||||||
spring damping-ratio=0.9 stiffness=800 epsilon=0.0001
|
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 {
|
window-close {
|
||||||
spring damping-ratio=0.9 stiffness=800 epsilon=0.0001
|
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 {
|
window-movement {
|
||||||
spring damping-ratio=0.9 stiffness=800 epsilon=0.0001
|
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 {
|
horizontal-view-movement {
|
||||||
spring damping-ratio=0.9 stiffness=800 epsilon=0.0001
|
spring damping-ratio=0.9 stiffness=800 epsilon=0.0001
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// workspace-switch: played when switching between vertical workspaces
|
||||||
|
// (Mod+Ctrl+J/K, Mod+1–0). Consistent spring keeps it feeling cohesive.
|
||||||
workspace-switch {
|
workspace-switch {
|
||||||
spring damping-ratio=0.9 stiffness=800 epsilon=0.0001
|
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 {
|
config-notification-open-close {
|
||||||
spring damping-ratio=0.9 stiffness=800 epsilon=0.0001
|
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 {
|
screenshot-ui-open {
|
||||||
duration-ms 200
|
duration-ms 200
|
||||||
curve "ease-out-cubic"
|
curve "ease-out-cubic"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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'"
|
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"
|
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"
|
spawn-at-startup "blueman-applet"
|
||||||
|
|
|
||||||
|
|
@ -1,74 +1,158 @@
|
||||||
// ── Keybindings ───────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// modules/binds.kdl — Keyboard and mouse bindings for niri
|
||||||
//
|
//
|
||||||
// Modifier semantics (directional keys):
|
// PURPOSE: Central definition of every user-triggered action in the niri
|
||||||
// Mod + dir → focus (window or column)
|
// session. Bindings are grouped by function so related keys are easy
|
||||||
// Mod + Shift + dir → move selected window/column
|
// to find and modify.
|
||||||
// Mod + Ctrl + dir → navigate workspaces
|
|
||||||
// Mod + Ctrl+Shift + dir → move window to workspace
|
|
||||||
// Mod + Alt + dir → resize (column width / window height)
|
|
||||||
//
|
//
|
||||||
|
// 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 {
|
binds {
|
||||||
// ── Applications ──────────────────────────────────────────────────────────
|
// ── Applications ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Mod+T: open a standard kitty terminal.
|
||||||
Mod+T { spawn "kitty"; }
|
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+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+M { spawn "bash" "-c" "kitty -e nvim"; }
|
||||||
|
|
||||||
|
// Mod+E: open Thunar file manager (primary GUI file manager).
|
||||||
Mod+E { spawn "thunar"; }
|
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+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+X { spawn "bash" "-c" "wofi --show=run"; }
|
||||||
|
|
||||||
|
// Mod+N: open Nextcloud desktop client for cloud sync management.
|
||||||
Mod+N { spawn "nextcloud"; }
|
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+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+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+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+R { spawn "bash" "-c" "vicinae toggle"; }
|
||||||
Mod+Return { 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"; }
|
Ctrl+Shift+R { spawn "bash" "-c" "vicinae toggle"; }
|
||||||
|
|
||||||
Mod+F { spawn "bash" "-c" "~/.config/scripts/wofi-file-search.sh"; }
|
// Mod+Shift+R: open wofi in drun mode (.desktop application launcher).
|
||||||
Mod+Shift+F { spawn "bash" "-c" "~/.config/scripts/foldersearch.sh"; }
|
Mod+Shift+R { spawn "bash" "-c" "wofi --show drun"; }
|
||||||
Mod+Alt+F { spawn "wofi-calc"; }
|
|
||||||
|
|
||||||
|
// 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+S { spawn "pavucontrol"; }
|
||||||
|
|
||||||
|
// Mod+U: open btop system monitor inside kitty.
|
||||||
Mod+U { spawn "bash" "-c" "kitty -e btop"; }
|
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+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+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+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+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+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"; }
|
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+D { spawn "bash" "-c" "~/.config/scripts/drawer.sh"; }
|
||||||
Mod+Shift+A { spawn "bash" "-c" "~/.config/scripts/drawer.sh"; }
|
Mod+Shift+A { spawn "bash" "-c" "~/.config/scripts/drawer.sh"; }
|
||||||
|
|
||||||
// ── Screenshots ───────────────────────────────────────────────────────────
|
// ── Screenshots ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Print / Mod+P: interactive region/window screenshot (saved to Pictures/Screenshots).
|
||||||
Print { screenshot; }
|
Print { screenshot; }
|
||||||
Mod+P { screenshot; }
|
Mod+P { screenshot; }
|
||||||
|
|
||||||
|
// Mod+Shift+P: full-screen (current output) screenshot without selection UI.
|
||||||
Mod+Shift+P { screenshot-screen; }
|
Mod+Shift+P { screenshot-screen; }
|
||||||
|
|
||||||
// ── Window Management ─────────────────────────────────────────────────────
|
// ── 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+Q { close-window; }
|
||||||
Mod+Shift+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+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+Shift+V { center-column; }
|
||||||
Mod+C { 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"; }
|
Mod+Ctrl+M { switch-layout "next"; }
|
||||||
|
|
||||||
// ── Niri layout actions ───────────────────────────────────────────────────
|
// ── 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+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+Shift+Backspace { maximize-column; }
|
||||||
|
|
||||||
|
// Mod+Ctrl+Backspace: true fullscreen (window covers the whole output,
|
||||||
|
// hiding the eww bar).
|
||||||
Mod+Ctrl+Backspace { fullscreen-window; }
|
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+A { consume-window-into-column; }
|
||||||
Mod+Y { expel-window-from-column; }
|
Mod+Y { expel-window-from-column; }
|
||||||
Mod+Alt+C { consume-or-expel-window-left; }
|
Mod+Alt+C { consume-or-expel-window-left; }
|
||||||
Mod+Alt+V { consume-or-expel-window-right; }
|
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+H { focus-column-left; }
|
||||||
Mod+L { focus-column-right; }
|
Mod+L { focus-column-right; }
|
||||||
Mod+J { focus-window-down; }
|
Mod+J { focus-window-down; }
|
||||||
|
|
@ -78,11 +162,17 @@ binds {
|
||||||
Mod+Down { focus-window-down; }
|
Mod+Down { focus-window-down; }
|
||||||
Mod+Up { focus-window-up; }
|
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+Tab { focus-window-down-or-column-right; }
|
||||||
Mod+Shift+Tab { focus-window-up-or-column-left; }
|
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; }
|
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+H { move-column-left; }
|
||||||
Mod+Shift+L { move-column-right; }
|
Mod+Shift+L { move-column-right; }
|
||||||
Mod+Shift+J { move-window-down; }
|
Mod+Shift+J { move-window-down; }
|
||||||
|
|
@ -92,12 +182,17 @@ binds {
|
||||||
Mod+Shift+Down { move-window-down; }
|
Mod+Shift+Down { move-window-down; }
|
||||||
Mod+Shift+Up { move-window-up; }
|
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+Button272 { move-window-with-pointer; }
|
||||||
Mod+Button273 { resize-window-with-pointer; }
|
Mod+Button273 { resize-window-with-pointer; }
|
||||||
Mod+Shift+Button272 { 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+L { set-column-width "+10%"; }
|
||||||
Mod+Alt+H { set-column-width "-10%"; }
|
Mod+Alt+H { set-column-width "-10%"; }
|
||||||
Mod+Alt+K { set-window-height "-10%"; }
|
Mod+Alt+K { set-window-height "-10%"; }
|
||||||
|
|
@ -107,7 +202,8 @@ binds {
|
||||||
Mod+Alt+Up { set-window-height "-10%"; }
|
Mod+Alt+Up { set-window-height "-10%"; }
|
||||||
Mod+Alt+Down { 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+1 { focus-workspace 1; }
|
||||||
Mod+2 { focus-workspace 2; }
|
Mod+2 { focus-workspace 2; }
|
||||||
Mod+3 { focus-workspace 3; }
|
Mod+3 { focus-workspace 3; }
|
||||||
|
|
@ -119,6 +215,7 @@ binds {
|
||||||
Mod+9 { focus-workspace 9; }
|
Mod+9 { focus-workspace 9; }
|
||||||
Mod+0 { focus-workspace 10; }
|
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+1 { move-column-to-workspace 1; }
|
||||||
Mod+Shift+2 { move-column-to-workspace 2; }
|
Mod+Shift+2 { move-column-to-workspace 2; }
|
||||||
Mod+Shift+3 { move-column-to-workspace 3; }
|
Mod+Shift+3 { move-column-to-workspace 3; }
|
||||||
|
|
@ -131,7 +228,9 @@ binds {
|
||||||
Mod+Shift+0 { move-column-to-workspace 10; }
|
Mod+Shift+0 { move-column-to-workspace 10; }
|
||||||
|
|
||||||
// ── Workspaces — relative navigation (Ctrl + direction) ──────────────────
|
// ── 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+J { focus-workspace-down; }
|
||||||
Mod+Ctrl+K { focus-workspace-up; }
|
Mod+Ctrl+K { focus-workspace-up; }
|
||||||
Mod+Ctrl+Down { focus-workspace-down; }
|
Mod+Ctrl+Down { focus-workspace-down; }
|
||||||
|
|
@ -142,7 +241,9 @@ binds {
|
||||||
Mod+Ctrl+Right { focus-workspace-down; }
|
Mod+Ctrl+Right { focus-workspace-down; }
|
||||||
Mod+Ctrl+Left { focus-workspace-up; }
|
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+J { move-column-to-workspace-down; }
|
||||||
Mod+Ctrl+Shift+K { move-column-to-workspace-up; }
|
Mod+Ctrl+Shift+K { move-column-to-workspace-up; }
|
||||||
Mod+Ctrl+Shift+Down { move-column-to-workspace-down; }
|
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+Right { move-column-to-workspace-down; }
|
||||||
Mod+Ctrl+Shift+Left { move-column-to-workspace-up; }
|
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+WheelScrollDown cooldown-ms=150 { focus-workspace-down; }
|
||||||
Mod+WheelScrollUp cooldown-ms=150 { focus-workspace-up; }
|
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+WheelScrollDown { move-column-to-workspace-down; }
|
||||||
Mod+Shift+WheelScrollUp { move-column-to-workspace-up; }
|
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+WheelScrollRight { focus-column-right; }
|
||||||
Mod+WheelScrollLeft { focus-column-left; }
|
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+XF86AudioRaiseVolume { focus-workspace-down; }
|
||||||
Mod+XF86AudioLowerVolume { focus-workspace-up; }
|
Mod+XF86AudioLowerVolume { focus-workspace-up; }
|
||||||
Mod+Shift+XF86AudioRaiseVolume { move-column-to-workspace-down; }
|
Mod+Shift+XF86AudioRaiseVolume { move-column-to-workspace-down; }
|
||||||
Mod+Shift+XF86AudioLowerVolume { move-column-to-workspace-up; }
|
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+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; }
|
Mod+Shift+Space { move-column-to-workspace-with-recent-window; }
|
||||||
|
// Overview toggle (bird's-eye all-workspaces view):
|
||||||
Mod+Ctrl+Space { toggle-overview; }
|
Mod+Ctrl+Space { toggle-overview; }
|
||||||
|
// Mute button + Mod as an additional overview toggle (useful on tablets).
|
||||||
Mod+XF86AudioMute { toggle-overview; }
|
Mod+XF86AudioMute { toggle-overview; }
|
||||||
Mod+Shift+XF86AudioMute { move-column-to-workspace-with-recent-window; }
|
Mod+Shift+XF86AudioMute { move-column-to-workspace-with-recent-window; }
|
||||||
|
|
||||||
// ── Bar / UI ──────────────────────────────────────────────────────────────
|
// ── 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+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"; }
|
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%+"; }
|
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%-"; }
|
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"; }
|
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"; }
|
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"; }
|
XF86AudioPlay allow-when-locked=true { spawn "playerctl" "play-pause" "-p" "spotify,vlc"; }
|
||||||
|
|
||||||
// ── Brightness ────────────────────────────────────────────────────────────
|
// ── 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"; }
|
XF86MonBrightnessUp allow-when-locked=true { spawn "bri" "--up"; }
|
||||||
XF86MonBrightnessDown allow-when-locked=true { spawn "bri" "--down"; }
|
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+X { spawn "bash" "-c" "killall gammastep; gammastep -O 6500"; }
|
||||||
Mod+Ctrl+W { spawn "bash" "-c" "killall gammastep; gammastep -O 5000"; }
|
Mod+Ctrl+W { spawn "bash" "-c" "killall gammastep; gammastep -O 5000"; }
|
||||||
Mod+Ctrl+A { spawn "bash" "-c" "killall gammastep; gammastep -O 4000"; }
|
Mod+Ctrl+A { spawn "bash" "-c" "killall gammastep; gammastep -O 4000"; }
|
||||||
Mod+Ctrl+Q { spawn "bash" "-c" "killall gammastep; gammastep -O 3000"; }
|
Mod+Ctrl+Q { spawn "bash" "-c" "killall gammastep; gammastep -O 3000"; }
|
||||||
Mod+Ctrl+S { spawn "bash" "-c" "killall gammastep; gammastep -O 2700"; }
|
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+I { spawn "chamel" "toggle"; }
|
||||||
Mod+Ctrl+U { spawn "chamel" "clear"; }
|
Mod+Ctrl+U { spawn "chamel" "clear"; }
|
||||||
Mod+Ctrl+Z { spawn "chamel" "clear-and-deactivate"; }
|
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+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+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+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+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"; }
|
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+E { spawn "bash" "-c" "~/.config/scripts/screenrotationwcw.sh"; }
|
||||||
Mod+Ctrl+D { spawn "bash" "-c" "~/.config/scripts/screenrotationacw.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+O { spawn "swaylock" "-f"; }
|
||||||
|
|
||||||
|
// Mod+Shift+O: quit niri (terminates the entire Wayland session).
|
||||||
Mod+Shift+O { quit; }
|
Mod+Shift+O { quit; }
|
||||||
|
|
||||||
|
// Mod+Ctrl+O: immediately power off the machine via systemctl.
|
||||||
Mod+Ctrl+O { spawn "systemctl" "poweroff"; }
|
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"; }
|
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; }
|
Mod+Alt+Ctrl+Shift+End { quit; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 Variables ─────────────────────────────────────────────────────
|
||||||
environment {
|
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"
|
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_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"
|
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_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"
|
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"
|
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"
|
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"
|
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"
|
GDK_SCALE "2"
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Cursor ────────────────────────────────────────────────────────────────────
|
// ── Cursor ────────────────────────────────────────────────────────────────────
|
||||||
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-theme "Nordzy-cursors-lefthand"
|
||||||
|
|
||||||
|
// xcursor-size: cursor size at compositor level, matching XCURSOR_SIZE above.
|
||||||
xcursor-size 40
|
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-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
|
hide-when-typing
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,12 @@
|
||||||
#!/usr/bin/env bash
|
#!/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
|
echo $1 | openssl aes-256-cbc -a -salt -pbkdf2 -pass pass:$2
|
||||||
|
|
|
||||||
127
nvim/init.lua
127
nvim/init.lua
|
|
@ -1,43 +1,56 @@
|
||||||
-- ── Bootstrap lazy.nvim ───────────────────────────────────────────────────────
|
-- ── 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"
|
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
|
||||||
if not (vim.uv or vim.loop).fs_stat(lazypath) then
|
if not (vim.uv or vim.loop).fs_stat(lazypath) then
|
||||||
vim.fn.system({
|
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",
|
"https://github.com/folke/lazy.nvim.git",
|
||||||
"--branch=stable", lazypath,
|
"--branch=stable", lazypath,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
-- Prepend lazy's path so Neovim can find and load it before any other plugin.
|
||||||
vim.opt.rtp:prepend(lazypath)
|
vim.opt.rtp:prepend(lazypath)
|
||||||
|
|
||||||
-- ── Plugins ───────────────────────────────────────────────────────────────────
|
-- ── Plugins ───────────────────────────────────────────────────────────────────
|
||||||
require("lazy").setup({
|
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") },
|
{ dir = vim.fn.expand("~/Dotfiles/nvim/theme/cyberqueer.nvim") },
|
||||||
"rktjmp/lush.nvim",
|
"rktjmp/lush.nvim", -- HSL-based colorscheme builder used by cyberqueer
|
||||||
"tpope/vim-sensible",
|
"tpope/vim-sensible", -- Sensible Vim defaults (better backspace, history, etc.)
|
||||||
"junegunn/goyo.vim",
|
"junegunn/goyo.vim", -- Distraction-free writing mode (centers and pads the buffer)
|
||||||
"arecarn/vim-crunch",
|
"arecarn/vim-crunch", -- Evaluate math expressions inside the buffer
|
||||||
"preservim/nerdtree",
|
"preservim/nerdtree", -- File-tree sidebar navigator
|
||||||
"ryanoasis/vim-devicons",
|
"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", build = function() vim.fn["fzf#install"]() end },
|
||||||
"junegunn/fzf.vim",
|
"junegunn/fzf.vim", -- :Files, :Rg, :Buffers etc. powered by fzf
|
||||||
"vim-airline/vim-airline",
|
"vim-airline/vim-airline", -- Status/tabline with Powerline-style separators
|
||||||
"vim-airline/vim-airline-themes",
|
"vim-airline/vim-airline-themes", -- Theme library for vim-airline (cyberqueer theme used)
|
||||||
"voldikss/vim-floaterm",
|
"voldikss/vim-floaterm", -- Floating terminal windows inside Neovim
|
||||||
"rust-lang/rust.vim",
|
"rust-lang/rust.vim", -- Rust syntax, fmt-on-save, and cargo integration
|
||||||
"norcalli/nvim-colorizer.lua",
|
"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" },
|
{ "neoclide/coc.nvim", branch = "release" },
|
||||||
|
-- vim-visual-multi: multiple-cursor editing (like Sublime Text ctrl+d)
|
||||||
{ "mg979/vim-visual-multi", branch = "master" },
|
{ "mg979/vim-visual-multi", branch = "master" },
|
||||||
"SirVer/ultisnips",
|
"SirVer/ultisnips", -- Snippet engine (expand/jump triggers configured below)
|
||||||
"honza/vim-snippets",
|
"honza/vim-snippets", -- Community snippet library consumed by UltiSnips
|
||||||
"mfussenegger/nvim-dap",
|
"mfussenegger/nvim-dap", -- Debug Adapter Protocol client for step-through debugging
|
||||||
"elihunter173/dirbuf.nvim",
|
"elihunter173/dirbuf.nvim", -- Edit directory contents like a buffer (rename, delete, etc.)
|
||||||
"tpope/vim-dadbod",
|
"tpope/vim-dadbod", -- Database client (SQL queries against any DB URL)
|
||||||
"kristijanhusak/vim-dadbod-ui",
|
"kristijanhusak/vim-dadbod-ui", -- TUI for vim-dadbod (tree browser + result panes)
|
||||||
"kristijanhusak/vim-dadbod-completion",
|
"kristijanhusak/vim-dadbod-completion", -- CoC/native completion source for SQL via dadbod
|
||||||
"nvim-mini/mini.icons",
|
"nvim-mini/mini.icons", -- Minimal icon provider used by dadbod-ui and others
|
||||||
"tadmccorkle/markdown.nvim",
|
"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 },
|
{ "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",
|
"greggh/claude-code.nvim",
|
||||||
dependencies = { "nvim-lua/plenary.nvim" },
|
dependencies = { "nvim-lua/plenary.nvim" },
|
||||||
|
|
@ -46,61 +59,75 @@ require("lazy").setup({
|
||||||
end,
|
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" } },
|
install = { colorscheme = { "habamax" } },
|
||||||
})
|
})
|
||||||
|
|
||||||
-- ── Colorscheme & UI ──────────────────────────────────────────────────────────
|
-- ── Colorscheme & UI ──────────────────────────────────────────────────────────
|
||||||
vim.cmd("colorscheme cyberqueer")
|
vim.cmd("colorscheme cyberqueer")
|
||||||
|
|
||||||
|
-- Tell airline to use Powerline-patched glyphs for segment separators.
|
||||||
vim.g.airline_powerline_fonts = 1
|
vim.g.airline_powerline_fonts = 1
|
||||||
vim.g.airline_theme = "cyberqueer"
|
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 ipaddr = vim.trim(vim.fn.system("hostname -i"))
|
||||||
local hostname = vim.trim(vim.fn.system("hostname -s"))
|
local hostname = vim.trim(vim.fn.system("hostname -s"))
|
||||||
vim.g.airline_section_x = "IP:" .. ipaddr .. " DNS:" .. hostname
|
vim.g.airline_section_x = "IP:" .. ipaddr .. " DNS:" .. hostname
|
||||||
|
|
||||||
-- ── Providers ─────────────────────────────────────────────────────────────────
|
-- ── 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_ruby_provider = 0
|
||||||
vim.g.loaded_perl_provider = 0
|
vim.g.loaded_perl_provider = 0
|
||||||
|
|
||||||
|
|
||||||
-- ── Editor options ────────────────────────────────────────────────────────────
|
-- ── Editor options ────────────────────────────────────────────────────────────
|
||||||
|
-- Enable filetype-specific plugins and indentation rules (e.g. Python 4-space).
|
||||||
vim.cmd("filetype plugin indent on")
|
vim.cmd("filetype plugin indent on")
|
||||||
vim.cmd("syntax enable")
|
vim.cmd("syntax enable")
|
||||||
|
|
||||||
vim.opt.number = true
|
vim.opt.number = true -- show absolute line numbers in the gutter
|
||||||
vim.opt.relativenumber = true
|
vim.opt.relativenumber = true -- show relative numbers above/below for fast j/k jumps
|
||||||
vim.opt.cursorline = true
|
vim.opt.cursorline = true -- highlight the entire line the cursor is on
|
||||||
vim.opt.cursorcolumn = true
|
vim.opt.cursorcolumn = true -- highlight the entire column the cursor is in
|
||||||
vim.opt.showmode = false
|
vim.opt.showmode = false -- airline already shows mode; suppress the redundant "-- INSERT --" message
|
||||||
vim.opt.shiftwidth = 4
|
vim.opt.shiftwidth = 4 -- number of spaces for each indentation level (>> / <<)
|
||||||
vim.opt.scrolloff = 5
|
vim.opt.scrolloff = 5 -- keep at least 5 lines visible above/below the cursor
|
||||||
vim.opt.wrap = false
|
vim.opt.wrap = false -- disable line wrapping (horizontal scroll instead)
|
||||||
vim.opt.incsearch = true
|
vim.opt.incsearch = true -- show matches incrementally as you type the search pattern
|
||||||
vim.opt.ignorecase = true
|
vim.opt.ignorecase = true -- case-insensitive search by default
|
||||||
vim.opt.smartcase = true
|
vim.opt.smartcase = true -- override ignorecase when the pattern contains uppercase
|
||||||
vim.opt.showcmd = true
|
vim.opt.showcmd = true -- show the partial command being typed in the status line
|
||||||
vim.opt.showmatch = true
|
vim.opt.showmatch = true -- briefly jump to the matching bracket when one is typed
|
||||||
vim.opt.hlsearch = true
|
vim.opt.hlsearch = true -- highlight all search matches (clear with :noh)
|
||||||
vim.opt.history = 1000
|
vim.opt.history = 1000 -- keep 1000 entries in command and search history
|
||||||
vim.opt.wildmenu = true
|
vim.opt.wildmenu = true -- show a completion menu for : commands
|
||||||
vim.opt.wildmode = "list:longest"
|
vim.opt.wildmode = "list:longest" -- first tab lists completions, second completes to longest common prefix
|
||||||
|
|
||||||
-- ── Keymaps ───────────────────────────────────────────────────────────────────
|
-- ── Keymaps ───────────────────────────────────────────────────────────────────
|
||||||
-- window navigation (normal mode)
|
-- 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-l>", "<C-w>w")
|
||||||
vim.keymap.set("n", "<C-h>", "<C-w>h")
|
vim.keymap.set("n", "<C-h>", "<C-w>h")
|
||||||
vim.keymap.set("n", "<C-j>", "<C-w>j")
|
vim.keymap.set("n", "<C-j>", "<C-w>j")
|
||||||
vim.keymap.set("n", "<C-k>", "<C-w>k")
|
vim.keymap.set("n", "<C-k>", "<C-w>k")
|
||||||
|
|
||||||
-- window navigation from terminal-insert mode (exit insert, then move)
|
-- 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-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-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-k>", "<C-\\><C-n><C-w>k", { silent = true })
|
||||||
vim.keymap.set("t", "<C-l>", "<C-\\><C-n><C-w>w", { 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)
|
-- 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", {
|
vim.api.nvim_create_autocmd("BufEnter", {
|
||||||
pattern = "term://*",
|
pattern = "term://*",
|
||||||
callback = function()
|
callback = function()
|
||||||
|
|
@ -111,6 +138,8 @@ vim.api.nvim_create_autocmd("BufEnter", {
|
||||||
})
|
})
|
||||||
|
|
||||||
-- calendar.vim steals <C-hjkl> for month nav; restore window movement
|
-- 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", {
|
vim.api.nvim_create_autocmd("FileType", {
|
||||||
pattern = "calendar",
|
pattern = "calendar",
|
||||||
callback = function()
|
callback = function()
|
||||||
|
|
@ -122,23 +151,31 @@ vim.api.nvim_create_autocmd("FileType", {
|
||||||
end,
|
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 })
|
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 })
|
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 })
|
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()
|
vim.keymap.set("n", "q", function()
|
||||||
local ok = pcall(vim.cmd, "wq")
|
local ok = pcall(vim.cmd, "wq")
|
||||||
if not ok then vim.cmd("q") end
|
if not ok then vim.cmd("q") end
|
||||||
end, { silent = true })
|
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", "<TAB>", "<C-N>")
|
||||||
vim.keymap.set("i", "<S-TAB>", "<TAB>")
|
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 '%'")
|
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>")
|
vim.cmd("command! Vb normal! <C-v>")
|
||||||
|
|
||||||
-- ── UltiSnips ─────────────────────────────────────────────────────────────────
|
-- ── UltiSnips ─────────────────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
# Answerfile fields: drive, kernel, keymap, hostname, username, encrypt, fido2_root,
|
# Answerfile fields: drive, kernel, keymap, hostname, username, encrypt, fido2_root,
|
||||||
# fido2_user, run_tui (password always prompted interactively)
|
# 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
|
set -Eeuo pipefail
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
|
|
@ -20,22 +22,29 @@ LOGFILE="$HOME/arch-autoinstall.log"
|
||||||
echo "############################################"
|
echo "############################################"
|
||||||
echo
|
echo
|
||||||
} >> "$LOGFILE"
|
} >> "$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
|
exec > >(tee -a "$LOGFILE") 2>&1
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# Error handler — TUI prompt to send log via croc
|
# Error handler — TUI prompt to send log via croc
|
||||||
############################################
|
############################################
|
||||||
error_handler() {
|
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:-?}"
|
local exit_code=$? line_num="${1:-?}"
|
||||||
echo "" >> "$LOGFILE"
|
echo "" >> "$LOGFILE"
|
||||||
echo "ERROR: installer failed at line $line_num (exit code $exit_code)" >> "$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 command -v dialog &>/dev/null; then
|
||||||
if dialog --clear --title " Installer Error " \
|
if dialog --clear --title " Installer Error " \
|
||||||
--yesno \
|
--yesno \
|
||||||
"Installation failed at line $line_num (exit code: $exit_code).\n\nSend the log to another system via croc for analysis?" \
|
"Installation failed at line $line_num (exit code: $exit_code).\n\nSend the log to another system via croc for analysis?" \
|
||||||
9 62; then
|
9 62; then
|
||||||
clear
|
clear
|
||||||
|
# croc may not be present in the base live ISO — install it on demand.
|
||||||
if ! command -v croc &>/dev/null; then
|
if ! command -v croc &>/dev/null; then
|
||||||
echo "Installing croc..."
|
echo "Installing croc..."
|
||||||
pacman -Sy --noconfirm croc 2>/dev/null || true
|
pacman -Sy --noconfirm croc 2>/dev/null || true
|
||||||
|
|
@ -53,6 +62,7 @@ error_handler() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "Installation failed at line $line_num (exit code $exit_code)."
|
echo "Installation failed at line $line_num (exit code $exit_code)."
|
||||||
read -rp "Send log via croc? [y/N]: " _croc_ans
|
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
|
if [[ "${_croc_ans,,}" == "y" ]]; then
|
||||||
command -v croc &>/dev/null || pacman -Sy --noconfirm croc 2>/dev/null || true
|
command -v croc &>/dev/null || pacman -Sy --noconfirm croc 2>/dev/null || true
|
||||||
croc send "$LOGFILE" || true
|
croc send "$LOGFILE" || true
|
||||||
|
|
@ -62,17 +72,24 @@ error_handler() {
|
||||||
fi
|
fi
|
||||||
exit "$exit_code"
|
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
|
trap 'error_handler $LINENO' ERR
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# ANSWERFILE
|
# 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}"
|
ANSWERFILE="${ANSWERFILE:-/answerfile.json}"
|
||||||
AF_MODE=false
|
AF_MODE=false
|
||||||
[[ -f "$ANSWERFILE" ]] && AF_MODE=true
|
[[ -f "$ANSWERFILE" ]] && AF_MODE=true
|
||||||
|
|
||||||
af_get() {
|
af_get() {
|
||||||
# af_get <jq-filter> [default]
|
# 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
|
local val
|
||||||
val=$(jq -r "${1} // empty" "$ANSWERFILE" 2>/dev/null || true)
|
val=$(jq -r "${1} // empty" "$ANSWERFILE" 2>/dev/null || true)
|
||||||
if [[ -z "$val" ]]; then printf '%s' "${2:-}"; else printf '%s' "$val"; fi
|
if [[ -z "$val" ]]; then printf '%s' "${2:-}"; else printf '%s' "$val"; fi
|
||||||
|
|
@ -80,28 +97,38 @@ af_get() {
|
||||||
|
|
||||||
af_bool() {
|
af_bool() {
|
||||||
# Returns YES or NO from a JSON boolean field
|
# 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)
|
local val; val=$(jq -r "${1} // false" "$ANSWERFILE" 2>/dev/null || true)
|
||||||
[[ "$val" == "true" ]] && echo "YES" || echo "NO"
|
[[ "$val" == "true" ]] && echo "YES" || echo "NO"
|
||||||
}
|
}
|
||||||
|
|
||||||
get_mac_suffix() {
|
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
|
local mac
|
||||||
mac=$(ip link show 2>/dev/null \
|
mac=$(ip link show 2>/dev/null \
|
||||||
| awk '/^[0-9]+: [^l][^o]/{iface=1} iface && /link\/ether/{print $2; iface=0; exit}')
|
| 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//:/}"
|
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}"; }
|
part() { [[ "$1" == *nvme* || "$1" == *mmcblk* ]] && echo "${1}p${2}" || echo "${1}${2}"; }
|
||||||
|
|
||||||
if $AF_MODE; then
|
if $AF_MODE; then
|
||||||
echo "Answerfile detected: $ANSWERFILE"
|
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
|
command -v jq &>/dev/null || pacman -Sy --noconfirm jq
|
||||||
fi
|
fi
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# NETWORK CHECK
|
# 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 ! ping -c1 -W3 1.1.1.1 &>/dev/null; then
|
||||||
if $AF_MODE; then
|
if $AF_MODE; then
|
||||||
echo "Warning: no internet connection detected — continuing in answerfile mode."
|
echo "Warning: no internet connection detected — continuing in answerfile mode."
|
||||||
|
|
@ -123,6 +150,8 @@ fi
|
||||||
# KEYMAP
|
# KEYMAP
|
||||||
# To add more layouts: append "code|Display Name" to KEYMAPS
|
# 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=(
|
KEYMAPS=(
|
||||||
"us|English US"
|
"us|English US"
|
||||||
"de|German"
|
"de|German"
|
||||||
|
|
@ -133,19 +162,26 @@ if $AF_MODE; then
|
||||||
else
|
else
|
||||||
echo "Select keyboard layout:"
|
echo "Select keyboard layout:"
|
||||||
for i in "${!KEYMAPS[@]}"; do
|
for i in "${!KEYMAPS[@]}"; do
|
||||||
|
# %%|* strips everything from the first '|' onward → the keymap code.
|
||||||
_km_code="${KEYMAPS[$i]%%|*}"
|
_km_code="${KEYMAPS[$i]%%|*}"
|
||||||
|
# ##*| strips everything up to and including the last '|' → the display name.
|
||||||
_km_name="${KEYMAPS[$i]##*|}"
|
_km_name="${KEYMAPS[$i]##*|}"
|
||||||
printf " %d) %-14s (%s)\n" $((i+1)) "$_km_name" "$_km_code"
|
printf " %d) %-14s (%s)\n" $((i+1)) "$_km_name" "$_km_code"
|
||||||
done
|
done
|
||||||
read -rp "Choice [1]: " _KM_IDX
|
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 ))
|
_KM_IDX=$(( ${_KM_IDX:-1} - 1 ))
|
||||||
if (( _KM_IDX >= 0 && _KM_IDX < ${#KEYMAPS[@]} )); then
|
if (( _KM_IDX >= 0 && _KM_IDX < ${#KEYMAPS[@]} )); then
|
||||||
LIVE_KEYMAP="${KEYMAPS[$_KM_IDX]%%|*}"
|
LIVE_KEYMAP="${KEYMAPS[$_KM_IDX]%%|*}"
|
||||||
else
|
else
|
||||||
|
# Out-of-range input silently falls back to the first entry (us).
|
||||||
LIVE_KEYMAP="${KEYMAPS[0]%%|*}"
|
LIVE_KEYMAP="${KEYMAPS[0]%%|*}"
|
||||||
fi
|
fi
|
||||||
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"
|
loadkeys "$LIVE_KEYMAP"
|
||||||
|
# Also export as KEYMAP so the chroot heredoc can write /etc/vconsole.conf.
|
||||||
KEYMAP="$LIVE_KEYMAP"
|
KEYMAP="$LIVE_KEYMAP"
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
|
|
@ -153,22 +189,27 @@ KEYMAP="$LIVE_KEYMAP"
|
||||||
############################################
|
############################################
|
||||||
if $AF_MODE; then
|
if $AF_MODE; then
|
||||||
echo "WARNING: Automated install — all data on $(af_get '.drive' '/dev/?') will be ERASED."
|
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)"
|
echo "Proceeding in 5 seconds... (Ctrl-C to abort)"
|
||||||
sleep 5
|
sleep 5
|
||||||
else
|
else
|
||||||
echo "WARNING: This will ERASE ALL DATA on the selected drive!"
|
echo "WARNING: This will ERASE ALL DATA on the selected drive!"
|
||||||
read -rp "Type 'YES' to continue: " confirm
|
read -rp "Type 'YES' to continue: " confirm
|
||||||
|
# Require the exact string "YES" (case-sensitive) to prevent accidental wipes.
|
||||||
[[ "$confirm" == "YES" ]] || { echo "Aborted."; exit 1; }
|
[[ "$confirm" == "YES" ]] || { echo "Aborted."; exit 1; }
|
||||||
fi
|
fi
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# REQUIRED PACKAGES FOR INSTALL ENVIRONMENT
|
# 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
|
pacman -Sy --noconfirm parted cryptsetup libfido2 pam-u2f
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# DRIVE SELECTION
|
# DRIVE SELECTION
|
||||||
############################################
|
############################################
|
||||||
|
# Print current block device layout so the operator can confirm the target drive.
|
||||||
lsblk
|
lsblk
|
||||||
if $AF_MODE && [[ -n "$(af_get '.drive')" ]]; then
|
if $AF_MODE && [[ -n "$(af_get '.drive')" ]]; then
|
||||||
DRIVE=$(af_get '.drive')
|
DRIVE=$(af_get '.drive')
|
||||||
|
|
@ -183,6 +224,8 @@ fi
|
||||||
if $AF_MODE; then
|
if $AF_MODE; then
|
||||||
KERNEL=$(af_get '.kernel' 'linux')
|
KERNEL=$(af_get '.kernel' 'linux')
|
||||||
RAW_HOSTNAME=$(af_get '.hostname' '')
|
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
|
if [[ -n "$RAW_HOSTNAME" ]]; then
|
||||||
HOSTNAME="${RAW_HOSTNAME}-$(get_mac_suffix)"
|
HOSTNAME="${RAW_HOSTNAME}-$(get_mac_suffix)"
|
||||||
else
|
else
|
||||||
|
|
@ -202,6 +245,8 @@ else
|
||||||
read -rp "Enter hostname: " HOSTNAME
|
read -rp "Enter hostname: " HOSTNAME
|
||||||
read -rp "Enter username: " USERNAME
|
read -rp "Enter username: " USERNAME
|
||||||
read -rp "Enable disk encryption? (YES/NO): " ENCRYPT_DISK
|
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"
|
FIDO_ROOT="NO"
|
||||||
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
|
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
|
||||||
read -rp "Enable FIDO2 unlocking for root partition? (YES/NO): " FIDO_ROOT
|
read -rp "Enable FIDO2 unlocking for root partition? (YES/NO): " FIDO_ROOT
|
||||||
|
|
@ -213,8 +258,11 @@ fi
|
||||||
read -rp "Enter password for $USERNAME: " USERPASS
|
read -rp "Enter password for $USERNAME: " USERPASS
|
||||||
[[ -z "$USERPASS" ]] && { echo "Error: password cannot be empty."; exit 1; }
|
[[ -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
|
if ! $AF_MODE; then
|
||||||
read -rp "Run dotfiles TUI setup inside chroot now? [YES/no]: " _RUN_TUI_IN
|
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="${_RUN_TUI_IN:-YES}"
|
||||||
[[ "${_RUN_TUI_IN^^}" == "YES" ]] && RUN_TUI="YES" || RUN_TUI="NO"
|
[[ "${_RUN_TUI_IN^^}" == "YES" ]] && RUN_TUI="YES" || RUN_TUI="NO"
|
||||||
fi
|
fi
|
||||||
|
|
@ -222,12 +270,17 @@ fi
|
||||||
############################################
|
############################################
|
||||||
# RAM / PARTITION SIZING
|
# 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}')
|
RAM_GB=$(free --giga | awk '/^Mem:/ {print $2}')
|
||||||
|
# 15 GiB boot partition: large enough for multiple kernels, initramfs, and UKIs.
|
||||||
BOOT_SIZE=15GiB
|
BOOT_SIZE=15GiB
|
||||||
SWAP_SIZE="${RAM_GB}GiB"
|
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_SIZE=$(lsblk -b -dn -o SIZE "$DRIVE")
|
||||||
DISK_GIB=$((DISK_SIZE / 1024 / 1024 / 1024))
|
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))
|
ROOT_GIB=$((DISK_GIB - RAM_GB - 15))
|
||||||
if (( ROOT_GIB < 8 )); then
|
if (( ROOT_GIB < 8 )); then
|
||||||
echo "ERROR: Not enough disk space. Root would be only ${ROOT_GIB}GiB (need ≥8GiB)."
|
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
|
# PARTITION DISK
|
||||||
############################################
|
############################################
|
||||||
|
# --script suppresses interactive prompts; mklabel gpt wipes any existing table.
|
||||||
|
# Partition 1: ESP at 1MiB–15GiB, 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 \
|
parted "$DRIVE" --script mklabel gpt \
|
||||||
mkpart ESP fat32 1MiB 15GiB \
|
mkpart ESP fat32 1MiB 15GiB \
|
||||||
set 1 boot on \
|
set 1 boot on \
|
||||||
mkpart ROOT 15GiB "$((15 + ROOT_GIB))"GiB \
|
mkpart ROOT 15GiB "$((15 + ROOT_GIB))"GiB \
|
||||||
mkpart SWAP "$((15 + ROOT_GIB))"GiB 100%
|
mkpart SWAP "$((15 + ROOT_GIB))"GiB 100%
|
||||||
|
|
||||||
|
# Resolve partition device paths via the `part` helper (handles NVMe 'p' suffix).
|
||||||
BOOT_PART=$(part "$DRIVE" 1)
|
BOOT_PART=$(part "$DRIVE" 1)
|
||||||
ROOT_PART=$(part "$DRIVE" 2)
|
ROOT_PART=$(part "$DRIVE" 2)
|
||||||
SWAP_PART=$(part "$DRIVE" 3)
|
SWAP_PART=$(part "$DRIVE" 3)
|
||||||
|
|
@ -255,7 +315,9 @@ SWAP_PART=$(part "$DRIVE" 3)
|
||||||
############################################
|
############################################
|
||||||
# FORMAT BOOT + SWAP
|
# FORMAT BOOT + SWAP
|
||||||
############################################
|
############################################
|
||||||
|
# FAT32 is required for the EFI System Partition; -F32 sets the FAT variant.
|
||||||
mkfs.fat -F32 "$BOOT_PART"
|
mkfs.fat -F32 "$BOOT_PART"
|
||||||
|
# mkswap writes the swap header; swapon activates it for use by pacstrap.
|
||||||
mkswap "$SWAP_PART"
|
mkswap "$SWAP_PART"
|
||||||
swapon "$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
|
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
|
||||||
echo "Encrypting root partition..."
|
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"
|
cryptsetup -v luksFormat "$ROOT_PART"
|
||||||
|
# Open the container and expose it as /dev/mapper/cryptroot for formatting.
|
||||||
cryptsetup open "$ROOT_PART" cryptroot
|
cryptsetup open "$ROOT_PART" cryptroot
|
||||||
|
|
||||||
# ── Auto-generate backup LUKS key ──────────────────────────────────────────
|
# ── 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
|
# 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.
|
# new system (inside the encrypted container) where only root can read it.
|
||||||
LUKS_BACKUP_KEY=$(mktemp /tmp/luks-backup-key.XXXXXX)
|
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"
|
dd if=/dev/urandom bs=64 count=1 2>/dev/null | base64 -w0 > "$LUKS_BACKUP_KEY"
|
||||||
echo "Enrolling auto-generated backup LUKS 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"
|
cryptsetup luksAddKey "$ROOT_PART" "$LUKS_BACKUP_KEY"
|
||||||
|
|
||||||
# ── Optional FIDO2 enrollment ─────────────────────────────────────────────
|
# ── Optional FIDO2 enrollment ─────────────────────────────────────────────
|
||||||
if [[ "$FIDO_ROOT" == "YES" ]]; then
|
if [[ "$FIDO_ROOT" == "YES" ]]; then
|
||||||
echo "Insert FIDO2 key for LUKS and touch when prompted..."
|
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
|
systemd-cryptenroll "$ROOT_PART" --fido2-device=auto --fido2-with-client-pin=no
|
||||||
fi
|
fi
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# BTRFS ON ENCRYPTED ROOT
|
# BTRFS ON ENCRYPTED ROOT
|
||||||
############################################
|
############################################
|
||||||
|
# Format the decrypted mapper device, not the raw partition.
|
||||||
mkfs.btrfs /dev/mapper/cryptroot
|
mkfs.btrfs /dev/mapper/cryptroot
|
||||||
|
# Mount flat (no subvolume) first so we can create the subvolume layout.
|
||||||
mount /dev/mapper/cryptroot /mnt
|
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/@
|
||||||
btrfs subvolume create /mnt/@home
|
btrfs subvolume create /mnt/@home
|
||||||
|
# Unmount the flat view before remounting through the subvolumes.
|
||||||
umount /mnt
|
umount /mnt
|
||||||
|
|
||||||
|
# Mount each subvolume at its final path for pacstrap to populate.
|
||||||
mount -o subvol=@ /dev/mapper/cryptroot /mnt
|
mount -o subvol=@ /dev/mapper/cryptroot /mnt
|
||||||
mkdir -p /mnt/home
|
mkdir -p /mnt/home
|
||||||
mount -o subvol=@home /dev/mapper/cryptroot /mnt/home
|
mount -o subvol=@home /dev/mapper/cryptroot /mnt/home
|
||||||
|
|
@ -303,6 +381,7 @@ else
|
||||||
############################################
|
############################################
|
||||||
# BTRFS ON UNENCRYPTED ROOT
|
# BTRFS ON UNENCRYPTED ROOT
|
||||||
############################################
|
############################################
|
||||||
|
# Same subvolume layout as the encrypted branch for consistency.
|
||||||
mkfs.btrfs "$ROOT_PART"
|
mkfs.btrfs "$ROOT_PART"
|
||||||
mount "$ROOT_PART" /mnt
|
mount "$ROOT_PART" /mnt
|
||||||
btrfs subvolume create /mnt/@
|
btrfs subvolume create /mnt/@
|
||||||
|
|
@ -315,11 +394,15 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p /mnt/boot
|
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
|
mount "$BOOT_PART" /mnt/boot
|
||||||
|
|
||||||
# Place backup key inside the new system (only accessible when disk is unlocked)
|
# Place backup key inside the new system (only accessible when disk is unlocked)
|
||||||
if [[ -n "$LUKS_BACKUP_KEY" ]]; then
|
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
|
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"
|
rm -f "$LUKS_BACKUP_KEY"
|
||||||
echo "Backup LUKS key written to /_LUKS_BACKUP_KEY in new system."
|
echo "Backup LUKS key written to /_LUKS_BACKUP_KEY in new system."
|
||||||
fi
|
fi
|
||||||
|
|
@ -327,11 +410,14 @@ fi
|
||||||
############################################
|
############################################
|
||||||
# GPU DETECTION
|
# 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_INFO=$(lspci | grep -E "VGA|3D" || true)
|
||||||
GPU_PKGS=""
|
GPU_PKGS=""
|
||||||
if echo "$GPU_INFO" | grep -qi "NVIDIA"; then
|
if echo "$GPU_INFO" | grep -qi "NVIDIA"; then
|
||||||
GPU_PKGS="nvidia nvidia-utils"
|
GPU_PKGS="nvidia nvidia-utils"
|
||||||
elif echo "$GPU_INFO" | grep -qi "AMD"; then
|
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"
|
GPU_PKGS="xf86-video-amdgpu"
|
||||||
elif echo "$GPU_INFO" | grep -qi "Intel"; then
|
elif echo "$GPU_INFO" | grep -qi "Intel"; then
|
||||||
GPU_PKGS="xf86-video-intel"
|
GPU_PKGS="xf86-video-intel"
|
||||||
|
|
@ -341,6 +427,8 @@ echo "Detected GPU: ${GPU_INFO:-none}"
|
||||||
############################################
|
############################################
|
||||||
# BASE INSTALL
|
# 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
|
# shellcheck disable=SC2086
|
||||||
pacstrap -K /mnt base base-devel "$KERNEL" linux-firmware vim bash zsh git less btop fastfetch \
|
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
|
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
|
# 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
|
genfstab -U /mnt >> /mnt/etc/fstab
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# COPY ANSWERFILE INTO NEW SYSTEM
|
# 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
|
if $AF_MODE; then
|
||||||
install -m 644 "$ANSWERFILE" /mnt/answerfile.json
|
install -m 644 "$ANSWERFILE" /mnt/answerfile.json
|
||||||
fi
|
fi
|
||||||
|
|
@ -360,48 +452,69 @@ fi
|
||||||
############################################
|
############################################
|
||||||
# PASS VARIABLES INTO CHROOT
|
# 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
|
export HOSTNAME USERNAME USERPASS ROOT_PART KERNEL FIDO_ROOT FIDO_USER ENCRYPT_DISK KEYMAP
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# CHROOT CONFIGURATION
|
# 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'
|
arch-chroot /mnt /bin/bash <<'CHROOT_EOF'
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Locale
|
# 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
|
echo "en_US.UTF-8 UTF-8" > /etc/locale.gen
|
||||||
locale-gen
|
locale-gen
|
||||||
echo "LANG=en_US.UTF-8" > /etc/locale.conf
|
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
|
echo "KEYMAP=${KEYMAP}" > /etc/vconsole.conf
|
||||||
|
|
||||||
# Time / hostname
|
# Time / hostname
|
||||||
|
# Symlink the timezone file so /etc/localtime always points at the correct zoneinfo.
|
||||||
ln -sf /usr/share/zoneinfo/Europe/Vienna /etc/localtime
|
ln -sf /usr/share/zoneinfo/Europe/Vienna /etc/localtime
|
||||||
|
# --systohc writes the current system time to the hardware clock (RTC).
|
||||||
hwclock --systohc
|
hwclock --systohc
|
||||||
echo "$HOSTNAME" > /etc/hostname
|
echo "$HOSTNAME" > /etc/hostname
|
||||||
|
|
||||||
# NetworkManager
|
# NetworkManager
|
||||||
|
# Enable at boot so the system has networking on first login without manual setup.
|
||||||
systemctl enable NetworkManager
|
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..."
|
echo "Cloning dotfiles into /etc/skel..."
|
||||||
git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git /etc/skel/Dotfiles \
|
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."
|
|| echo "Warning: dotfiles clone into skel failed — will fall back to direct clone."
|
||||||
# Seed /etc/skel with base shell dotfiles from the repo clone
|
# Seed /etc/skel with base shell dotfiles from the repo clone
|
||||||
if [[ -d /etc/skel/Dotfiles ]]; then
|
if [[ -d /etc/skel/Dotfiles ]]; then
|
||||||
D=/etc/skel/Dotfiles
|
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/.zshrc" /etc/skel/.zshrc 2>/dev/null || true
|
||||||
cp "$D/.bashrc" /etc/skel/.bashrc 2>/dev/null || true
|
cp "$D/.bashrc" /etc/skel/.bashrc 2>/dev/null || true
|
||||||
cp "$D/.vimrc" /etc/skel/.vimrc 2>/dev/null || true
|
cp "$D/.vimrc" /etc/skel/.vimrc 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# User
|
# User
|
||||||
|
# -m: create home dir; -G wheel: add to sudoers group; -s /bin/zsh: default shell.
|
||||||
useradd -m -G wheel -s /bin/zsh "$USERNAME"
|
useradd -m -G wheel -s /bin/zsh "$USERNAME"
|
||||||
|
# chpasswd reads "user:pass" from stdin to set the password non-interactively.
|
||||||
echo "$USERNAME:$USERPASS" | chpasswd
|
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
|
echo "%wheel ALL=(ALL:ALL) ALL" >> /etc/sudoers
|
||||||
|
|
||||||
###################################################
|
###################################################
|
||||||
# INITRAMFS CONFIG
|
# 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
|
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
|
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
|
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
|
sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect microcode modconf kms consolefont block btrfs filesystems keyboard fsck)/' /etc/mkinitcpio.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Regenerate the initramfs for the selected kernel preset.
|
||||||
mkinitcpio -p "$KERNEL"
|
mkinitcpio -p "$KERNEL"
|
||||||
|
|
||||||
###################################################
|
###################################################
|
||||||
# GRUB CONFIG
|
# GRUB CONFIG
|
||||||
###################################################
|
###################################################
|
||||||
|
# Read the raw partition UUID; used in kernel cmdline for stable device identification.
|
||||||
UUID=$(blkid -s UUID -o value "$ROOT_PART")
|
UUID=$(blkid -s UUID -o value "$ROOT_PART")
|
||||||
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
|
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
|
||||||
if [[ "$FIDO_ROOT" == "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"
|
KERNEL_CMD="rd.luks.name=${UUID}=cryptroot root=/dev/mapper/cryptroot"
|
||||||
else
|
else
|
||||||
|
# Classic initramfs encrypt hook syntax: cryptdevice=UUID=<UUID>:<name>.
|
||||||
KERNEL_CMD="cryptdevice=UUID=${UUID}:cryptroot root=/dev/mapper/cryptroot"
|
KERNEL_CMD="cryptdevice=UUID=${UUID}:cryptroot root=/dev/mapper/cryptroot"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
|
# Unencrypted btrfs: root= by UUID and rootflags to select the @ subvolume.
|
||||||
KERNEL_CMD="root=UUID=${UUID} rootflags=subvol=@"
|
KERNEL_CMD="root=UUID=${UUID} rootflags=subvol=@"
|
||||||
fi
|
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
|
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
|
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
|
grub-mkconfig -o /boot/grub/grub.cfg
|
||||||
|
|
||||||
###################################################
|
###################################################
|
||||||
|
|
@ -437,23 +560,23 @@ grub-mkconfig -o /boot/grub/grub.cfg
|
||||||
if [[ "$FIDO_USER" == "YES" ]]; then
|
if [[ "$FIDO_USER" == "YES" ]]; then
|
||||||
mkdir -p "/home/$USERNAME/.config/Yubico"
|
mkdir -p "/home/$USERNAME/.config/Yubico"
|
||||||
echo "Insert FIDO2 key for user login and touch when prompted..."
|
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"
|
sudo -u "$USERNAME" pamu2fcfg -u "$USERNAME" > "/home/$USERNAME/.config/Yubico/u2f_keys"
|
||||||
chown "$USERNAME":"$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
|
echo "auth required pam_u2f.so" >> /etc/pam.d/system-local-login
|
||||||
fi
|
fi
|
||||||
|
|
||||||
###################################################
|
###################################################
|
||||||
# CLONE DOTFILES
|
# CLONE DOTFILES
|
||||||
###################################################
|
###################################################
|
||||||
if [[ -d "/home/$USERNAME/Dotfiles" ]]; then
|
# Fall back to a direct clone if /etc/skel/Dotfiles was not copied into home
|
||||||
echo "Dotfiles already in home via skel — fixing ownership."
|
# (happens when the skel clone failed or useradd did not copy skel).
|
||||||
chown -R "$USERNAME:$USERNAME" "/home/$USERNAME"
|
echo "Cloning dotfiles..."
|
||||||
else
|
git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git "/home/$USERNAME/Dotfiles" \
|
||||||
echo "Cloning dotfiles directly to user home (skel clone failed)..."
|
&& chown -R "$USERNAME":"$USERNAME" "/home/$USERNAME/Dotfiles" \
|
||||||
git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git "/home/$USERNAME/Dotfiles" \
|
|| echo "Warning: dotfiles clone failed — clone manually after first boot."
|
||||||
&& chown -R "$USERNAME:$USERNAME" "/home/$USERNAME/Dotfiles" \
|
|
||||||
|| echo "Warning: dotfiles clone failed — clone manually after first boot."
|
|
||||||
fi
|
|
||||||
|
|
||||||
CHROOT_EOF
|
CHROOT_EOF
|
||||||
|
|
||||||
|
|
@ -461,14 +584,20 @@ CHROOT_EOF
|
||||||
# DOTFILES TUI SETUP (in-chroot, optional)
|
# DOTFILES TUI SETUP (in-chroot, optional)
|
||||||
############################################
|
############################################
|
||||||
if [[ "${RUN_TUI^^}" == "YES" ]]; then
|
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" \
|
echo "${USERNAME} ALL=(ALL) NOPASSWD: ALL" \
|
||||||
| arch-chroot /mnt tee /etc/sudoers.d/99-setup-nopasswd > /dev/null
|
| arch-chroot /mnt tee /etc/sudoers.d/99-setup-nopasswd > /dev/null
|
||||||
|
|
||||||
echo "Running tui-install.sh as ${USERNAME} inside chroot..."
|
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}" -- \
|
arch-chroot /mnt runuser -u "${USERNAME}" -- \
|
||||||
bash "/home/${USERNAME}/Dotfiles/setup/tui-install.sh" \
|
bash "/home/${USERNAME}/Dotfiles/setup/tui-install.sh" \
|
||||||
|| echo "Warning: tui-install exited with errors — check ~/dotfiles-install.log in the new system."
|
|| 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
|
arch-chroot /mnt rm -f /etc/sudoers.d/99-setup-nopasswd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -507,6 +636,8 @@ echo "Log file: $LOGFILE"
|
||||||
echo "############################################"
|
echo "############################################"
|
||||||
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
|
cp "$LOGFILE" /mnt/boot/ 2>/dev/null || true
|
||||||
|
|
||||||
echo "Installation complete! Unmount and reboot:"
|
echo "Installation complete! Unmount and reboot:"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
# If /answerfile.json exists (e.g. embedded via build.sh --preconf), all prompts
|
# If /answerfile.json exists (e.g. embedded via build.sh --preconf), all prompts
|
||||||
# are answered from it. Missing fields fall back to interactive 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
|
set -Eeuo pipefail
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
|
|
@ -17,12 +19,17 @@ LOGFILE="$HOME/archbaseos-guided-install.log"
|
||||||
echo "############################################"
|
echo "############################################"
|
||||||
echo
|
echo
|
||||||
} >> "$LOGFILE"
|
} >> "$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
|
exec > >(tee -a "$LOGFILE") 2>&1
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# Error handler — TUI prompt to send log via croc
|
# Error handler — TUI prompt to send log via croc
|
||||||
############################################
|
############################################
|
||||||
error_handler() {
|
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:-?}"
|
local exit_code=$? line_num="${1:-?}"
|
||||||
echo "" >> "$LOGFILE"
|
echo "" >> "$LOGFILE"
|
||||||
echo "ERROR: installer failed at line $line_num (exit code $exit_code)" >> "$LOGFILE"
|
echo "ERROR: installer failed at line $line_num (exit code $exit_code)" >> "$LOGFILE"
|
||||||
|
|
@ -30,7 +37,9 @@ error_handler() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "Installation failed at line $line_num (exit code $exit_code)."
|
echo "Installation failed at line $line_num (exit code $exit_code)."
|
||||||
read -rp "Send log via croc? [y/N]: " _croc_ans
|
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
|
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
|
command -v croc &>/dev/null || pacman -Sy --noconfirm croc 2>/dev/null || true
|
||||||
croc send "$LOGFILE" || true
|
croc send "$LOGFILE" || true
|
||||||
else
|
else
|
||||||
|
|
@ -38,6 +47,8 @@ error_handler() {
|
||||||
fi
|
fi
|
||||||
exit "$exit_code"
|
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
|
trap 'error_handler $LINENO' ERR
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
|
|
@ -45,12 +56,14 @@ trap 'error_handler $LINENO' ERR
|
||||||
############################################
|
############################################
|
||||||
|
|
||||||
confirm() {
|
confirm() {
|
||||||
|
# Require the exact string "YES" before erasing $1 — prevents accidental wipes.
|
||||||
echo "WARNING: This will ERASE ALL DATA on $1"
|
echo "WARNING: This will ERASE ALL DATA on $1"
|
||||||
read -rp "Type YES to continue: " ans
|
read -rp "Type YES to continue: " ans
|
||||||
[[ $ans == "YES" ]]
|
[[ $ans == "YES" ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
ask() {
|
ask() {
|
||||||
|
# Thin wrapper around read -rp so callers can capture the result via $().
|
||||||
local prompt=$1
|
local prompt=$1
|
||||||
local var
|
local var
|
||||||
read -rp "$prompt: " var
|
read -rp "$prompt: " var
|
||||||
|
|
@ -58,6 +71,8 @@ ask() {
|
||||||
}
|
}
|
||||||
|
|
||||||
pause() {
|
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..."
|
read -rp "Press ENTER to continue..."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,30 +83,41 @@ part() { [[ "$1" == *nvme* || "$1" == *mmcblk* ]] && echo "${1}p${2}" || echo "$
|
||||||
############################################
|
############################################
|
||||||
# ANSWERFILE
|
# 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}"
|
ANSWERFILE="${ANSWERFILE:-/answerfile.json}"
|
||||||
AF_MODE=false
|
AF_MODE=false
|
||||||
[[ -f "$ANSWERFILE" ]] && AF_MODE=true
|
[[ -f "$ANSWERFILE" ]] && AF_MODE=true
|
||||||
|
|
||||||
af_get() {
|
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
|
local val
|
||||||
val=$(jq -r "${1} // empty" "$ANSWERFILE" 2>/dev/null || true)
|
val=$(jq -r "${1} // empty" "$ANSWERFILE" 2>/dev/null || true)
|
||||||
if [[ -z "$val" ]]; then printf '%s' "${2:-}"; else printf '%s' "$val"; fi
|
if [[ -z "$val" ]]; then printf '%s' "${2:-}"; else printf '%s' "$val"; fi
|
||||||
}
|
}
|
||||||
|
|
||||||
af_bool() {
|
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)
|
local val; val=$(jq -r "${1} // false" "$ANSWERFILE" 2>/dev/null || true)
|
||||||
[[ "$val" == "true" ]] && echo "YES" || echo "NO"
|
[[ "$val" == "true" ]] && echo "YES" || echo "NO"
|
||||||
}
|
}
|
||||||
|
|
||||||
get_mac_suffix() {
|
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
|
local mac
|
||||||
mac=$(ip link show 2>/dev/null \
|
mac=$(ip link show 2>/dev/null \
|
||||||
| awk '/^[0-9]+: [^l][^o]/{iface=1} iface && /link\/ether/{print $2; iface=0; exit}')
|
| 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//:/}"
|
printf '%s' "${mac//:/}"
|
||||||
}
|
}
|
||||||
|
|
||||||
if $AF_MODE; then
|
if $AF_MODE; then
|
||||||
echo "== Arch Linux Guided Installer (answerfile mode) =="
|
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
|
command -v jq &>/dev/null || pacman -Sy --noconfirm jq
|
||||||
else
|
else
|
||||||
echo "== Arch Linux FIDO2-Ready Installer =="
|
echo "== Arch Linux FIDO2-Ready Installer =="
|
||||||
|
|
@ -100,6 +126,8 @@ fi
|
||||||
############################################
|
############################################
|
||||||
# NETWORK CHECK
|
# 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 ! ping -c1 -W3 1.1.1.1 &>/dev/null; then
|
||||||
if $AF_MODE; then
|
if $AF_MODE; then
|
||||||
echo "Warning: no internet connection detected — continuing in answerfile mode."
|
echo "Warning: no internet connection detected — continuing in answerfile mode."
|
||||||
|
|
@ -121,6 +149,8 @@ fi
|
||||||
# Begin
|
# 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=(
|
KEYMAPS=(
|
||||||
"us|English US"
|
"us|English US"
|
||||||
"de|German"
|
"de|German"
|
||||||
|
|
@ -132,6 +162,8 @@ KEYMAPS=(
|
||||||
if $AF_MODE; then
|
if $AF_MODE; then
|
||||||
KERNEL=$(af_get '.kernel' 'linux')
|
KERNEL=$(af_get '.kernel' 'linux')
|
||||||
RAW_HOSTNAME=$(af_get '.hostname' '')
|
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
|
if [[ -n "$RAW_HOSTNAME" ]]; then
|
||||||
HOSTNAME="${RAW_HOSTNAME}-$(get_mac_suffix)"
|
HOSTNAME="${RAW_HOSTNAME}-$(get_mac_suffix)"
|
||||||
else
|
else
|
||||||
|
|
@ -151,6 +183,8 @@ else
|
||||||
HOSTNAME=$(ask "Hostname")
|
HOSTNAME=$(ask "Hostname")
|
||||||
USERNAME=$(ask "Username")
|
USERNAME=$(ask "Username")
|
||||||
read -rp "Enable disk encryption? (YES/NO): " ENCRYPT_DISK
|
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"
|
ENABLE_FIDO_ROOT="NO"
|
||||||
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
|
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
|
||||||
read -rp "Enable FIDO2 for unlocking root? (YES/NO): " ENABLE_FIDO_ROOT
|
read -rp "Enable FIDO2 for unlocking root? (YES/NO): " ENABLE_FIDO_ROOT
|
||||||
|
|
@ -160,44 +194,56 @@ else
|
||||||
echo ""
|
echo ""
|
||||||
echo "Select keyboard layout for installed system:"
|
echo "Select keyboard layout for installed system:"
|
||||||
for i in "${!KEYMAPS[@]}"; do
|
for i in "${!KEYMAPS[@]}"; do
|
||||||
|
# %%|* strips from the first '|' onward → keymap code.
|
||||||
_km_code="${KEYMAPS[$i]%%|*}"
|
_km_code="${KEYMAPS[$i]%%|*}"
|
||||||
|
# ##*| strips up to and including the last '|' → display name.
|
||||||
_km_name="${KEYMAPS[$i]##*|}"
|
_km_name="${KEYMAPS[$i]##*|}"
|
||||||
printf " %d) %-14s (%s)\n" $((i+1)) "$_km_name" "$_km_code"
|
printf " %d) %-14s (%s)\n" $((i+1)) "$_km_name" "$_km_code"
|
||||||
done
|
done
|
||||||
read -rp "Choice [1]: " _KM_CHOICE
|
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 ))
|
_KM_CHOICE=$(( ${_KM_CHOICE:-1} - 1 ))
|
||||||
if (( _KM_CHOICE >= 0 && _KM_CHOICE < ${#KEYMAPS[@]} )); then
|
if (( _KM_CHOICE >= 0 && _KM_CHOICE < ${#KEYMAPS[@]} )); then
|
||||||
KEYMAP="${KEYMAPS[$_KM_CHOICE]%%|*}"
|
KEYMAP="${KEYMAPS[$_KM_CHOICE]%%|*}"
|
||||||
else
|
else
|
||||||
|
# Out-of-range input silently falls back to the first entry (us).
|
||||||
KEYMAP="${KEYMAPS[0]%%|*}"
|
KEYMAP="${KEYMAPS[0]%%|*}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Password always prompted interactively — never stored in the answerfile.
|
||||||
read -rp "Password for $USERNAME: " USERPASS
|
read -rp "Password for $USERNAME: " USERPASS
|
||||||
[[ -z "$USERPASS" ]] && { echo "Error: password cannot be empty."; exit 1; }
|
[[ -z "$USERPASS" ]] && { echo "Error: password cannot be empty."; exit 1; }
|
||||||
|
|
||||||
|
# Print block device layout so the operator can visually confirm the target drive.
|
||||||
lsblk
|
lsblk
|
||||||
|
|
||||||
if $AF_MODE && [[ -n "$(af_get '.drive')" ]]; then
|
if $AF_MODE && [[ -n "$(af_get '.drive')" ]]; then
|
||||||
DRIVE=$(af_get '.drive')
|
DRIVE=$(af_get '.drive')
|
||||||
echo "Drive (from answerfile): $DRIVE"
|
echo "Drive (from answerfile): $DRIVE"
|
||||||
echo "WARNING: All data on $DRIVE will be erased. Proceeding in 5 seconds..."
|
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
|
sleep 5
|
||||||
else
|
else
|
||||||
DRIVE=$(ask "Enter install drive (e.g., /dev/sda)")
|
DRIVE=$(ask "Enter install drive (e.g., /dev/sda)")
|
||||||
confirm "$DRIVE" || exit 1
|
confirm "$DRIVE" || exit 1
|
||||||
fi
|
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
|
pacman -Syd --noconfirm parted cryptsetup libfido2 pam-u2f systemd-ukify jq
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# Partitioning
|
# Partitioning
|
||||||
############################################
|
############################################
|
||||||
|
|
||||||
|
# Read installed RAM in GiB for swap sizing; awk extracts the total-memory column.
|
||||||
RAM_GB=$(free --giga | awk '/Mem/ {print $2}')
|
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)}')
|
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
|
EFI_SIZE=5
|
||||||
SWAP_SIZE=$RAM_GB
|
SWAP_SIZE=$RAM_GB
|
||||||
ROOT_SIZE=$((DISK_GB - SWAP_SIZE - EFI_SIZE - 1))
|
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"
|
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 \
|
parted -s "$DRIVE" mklabel gpt \
|
||||||
mkpart EFI fat32 1MiB "${EFI_SIZE}GiB" \
|
mkpart EFI fat32 1MiB "${EFI_SIZE}GiB" \
|
||||||
set 1 esp on \
|
set 1 esp on \
|
||||||
mkpart ROOT "${EFI_SIZE}GiB" "$((EFI_SIZE + ROOT_SIZE))GiB" \
|
mkpart ROOT "${EFI_SIZE}GiB" "$((EFI_SIZE + ROOT_SIZE))GiB" \
|
||||||
mkpart SWAP "$((EFI_SIZE + ROOT_SIZE))GiB" 100%
|
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)
|
EFI_PART=$(part "$DRIVE" 1)
|
||||||
ROOT_PART=$(part "$DRIVE" 2)
|
ROOT_PART=$(part "$DRIVE" 2)
|
||||||
SWAP_PART=$(part "$DRIVE" 3)
|
SWAP_PART=$(part "$DRIVE" 3)
|
||||||
|
|
||||||
|
# FAT32 is mandated by the UEFI specification for the EFI System Partition.
|
||||||
mkfs.fat -F32 "$EFI_PART"
|
mkfs.fat -F32 "$EFI_PART"
|
||||||
|
# mkswap writes the swap header; swapon activates it immediately for use during install.
|
||||||
mkswap "$SWAP_PART"
|
mkswap "$SWAP_PART"
|
||||||
swapon "$SWAP_PART"
|
swapon "$SWAP_PART"
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# Encryption (optional)
|
# Encryption (optional)
|
||||||
############################################
|
############################################
|
||||||
|
# Initialise to empty; gets set to a temp file path only when encryption is active.
|
||||||
LUKS_BACKUP_KEY=""
|
LUKS_BACKUP_KEY=""
|
||||||
|
|
||||||
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
|
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
|
||||||
echo "Formatting LUKS2 root..."
|
echo "Formatting LUKS2 root..."
|
||||||
|
# --type luks2 uses the newer LUKS2 format required by systemd-cryptenroll for FIDO2.
|
||||||
cryptsetup luksFormat "$ROOT_PART" --type luks2
|
cryptsetup luksFormat "$ROOT_PART" --type luks2
|
||||||
|
# Open (decrypt) the container; exposes it as /dev/mapper/cryptroot for formatting.
|
||||||
cryptsetup open "$ROOT_PART" cryptroot
|
cryptsetup open "$ROOT_PART" cryptroot
|
||||||
|
|
||||||
# ── Auto-generate backup LUKS key ─────────────────────────────────────────
|
# ── 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)
|
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"
|
dd if=/dev/urandom bs=64 count=1 2>/dev/null | base64 -w0 > "$LUKS_BACKUP_KEY"
|
||||||
echo "Enrolling auto-generated backup LUKS 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"
|
cryptsetup luksAddKey "$ROOT_PART" "$LUKS_BACKUP_KEY"
|
||||||
|
|
||||||
if [[ "$ENABLE_FIDO_ROOT" == "YES" ]]; then
|
if [[ "$ENABLE_FIDO_ROOT" == "YES" ]]; then
|
||||||
echo "Enroll FIDO2 key for LUKS2"
|
echo "Enroll FIDO2 key for LUKS2"
|
||||||
|
# pause() gives the operator time to insert the FIDO2 hardware key before enrollment.
|
||||||
pause
|
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
|
systemd-cryptenroll "$ROOT_PART" --fido2-device=auto --fido2-with-client-pin=no
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Format btrfs on the decrypted mapper device, not the raw partition.
|
||||||
mkfs.btrfs /dev/mapper/cryptroot
|
mkfs.btrfs /dev/mapper/cryptroot
|
||||||
|
# Mount flat (no subvolume) first so the subvolume tree can be created.
|
||||||
mount /dev/mapper/cryptroot /mnt
|
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/@
|
||||||
btrfs subvolume create /mnt/@home
|
btrfs subvolume create /mnt/@home
|
||||||
|
# Unmount the flat view before remounting through named subvolumes.
|
||||||
umount /mnt
|
umount /mnt
|
||||||
|
|
||||||
|
# Remount each subvolume at its intended mountpoint for pacstrap to populate.
|
||||||
mount -o subvol=@ /dev/mapper/cryptroot /mnt
|
mount -o subvol=@ /dev/mapper/cryptroot /mnt
|
||||||
mkdir -p /mnt/home
|
mkdir -p /mnt/home
|
||||||
mount -o subvol=@home /dev/mapper/cryptroot /mnt/home
|
mount -o subvol=@home /dev/mapper/cryptroot /mnt/home
|
||||||
|
|
@ -258,6 +327,7 @@ if [[ "$ENCRYPT_DISK" == "YES" ]]; then
|
||||||
else
|
else
|
||||||
echo "Skipping encryption — formatting root directly."
|
echo "Skipping encryption — formatting root directly."
|
||||||
|
|
||||||
|
# Same btrfs subvolume layout as the encrypted path for consistency.
|
||||||
mkfs.btrfs "$ROOT_PART"
|
mkfs.btrfs "$ROOT_PART"
|
||||||
mount "$ROOT_PART" /mnt
|
mount "$ROOT_PART" /mnt
|
||||||
btrfs subvolume create /mnt/@
|
btrfs subvolume create /mnt/@
|
||||||
|
|
@ -270,11 +340,15 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p /mnt/boot
|
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
|
mount "$EFI_PART" /mnt/boot
|
||||||
|
|
||||||
# Place backup key inside the new system (readable only by root, inside LUKS container)
|
# Place backup key inside the new system (readable only by root, inside LUKS container)
|
||||||
if [[ -n "$LUKS_BACKUP_KEY" ]]; then
|
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
|
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"
|
rm -f "$LUKS_BACKUP_KEY"
|
||||||
echo "Backup LUKS key written to /_LUKS_BACKUP_KEY in new system."
|
echo "Backup LUKS key written to /_LUKS_BACKUP_KEY in new system."
|
||||||
fi
|
fi
|
||||||
|
|
@ -282,10 +356,13 @@ fi
|
||||||
############################################
|
############################################
|
||||||
# Base System Install
|
# 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_INFO=$(lspci | grep -E "VGA|3D" || true)
|
||||||
GPU_PKGS=""
|
GPU_PKGS=""
|
||||||
|
|
||||||
if echo "$GPU_INFO" | grep -qi nvidia; then
|
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"
|
GPU_PKGS="nvidia-open"
|
||||||
elif echo "$GPU_INFO" | grep -qi amd; then
|
elif echo "$GPU_INFO" | grep -qi amd; then
|
||||||
GPU_PKGS="xf86-video-amdgpu"
|
GPU_PKGS="xf86-video-amdgpu"
|
||||||
|
|
@ -293,16 +370,21 @@ elif echo "$GPU_INFO" | grep -qi intel; then
|
||||||
GPU_PKGS="xf86-video-intel"
|
GPU_PKGS="xf86-video-intel"
|
||||||
fi
|
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
|
# shellcheck disable=SC2086
|
||||||
pacstrap /mnt \
|
pacstrap /mnt \
|
||||||
base base-devel "$KERNEL" linux-firmware vim zsh git networkmanager grub efibootmgr \
|
base base-devel "$KERNEL" linux-firmware vim zsh git networkmanager grub efibootmgr \
|
||||||
btrfs-progs cryptsetup libfido2 pam-u2f sudo less jq $GPU_PKGS
|
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
|
genfstab -U /mnt >> /mnt/etc/fstab
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# COPY ANSWERFILE INTO NEW SYSTEM
|
# 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
|
if $AF_MODE; then
|
||||||
install -m 644 "$ANSWERFILE" /mnt/answerfile.json
|
install -m 644 "$ANSWERFILE" /mnt/answerfile.json
|
||||||
fi
|
fi
|
||||||
|
|
@ -310,8 +392,14 @@ fi
|
||||||
############################################
|
############################################
|
||||||
# CHROOT Configuration
|
# CHROOT Configuration
|
||||||
############################################
|
############################################
|
||||||
|
# Capture the root partition UUID before entering the chroot; used in GRUB cmdline.
|
||||||
ROOT_UUID=$(blkid -s UUID -o value "$ROOT_PART")
|
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 \
|
arch-chroot /mnt /usr/bin/env \
|
||||||
HOSTNAME="$HOSTNAME" \
|
HOSTNAME="$HOSTNAME" \
|
||||||
USERNAME="$USERNAME" \
|
USERNAME="$USERNAME" \
|
||||||
|
|
@ -326,16 +414,20 @@ arch-chroot /mnt /usr/bin/env \
|
||||||
|
|
||||||
set -euo pipefail
|
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
|
echo "en_US.UTF-8 UTF-8" > /etc/locale.gen
|
||||||
locale-gen
|
locale-gen
|
||||||
echo "LANG=en_US.UTF-8" > /etc/locale.conf
|
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
|
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
|
ln -sf /usr/share/zoneinfo/Europe/Vienna /etc/localtime
|
||||||
hwclock --systohc
|
hwclock --systohc
|
||||||
|
|
||||||
echo "$HOSTNAME" > /etc/hostname
|
echo "$HOSTNAME" > /etc/hostname
|
||||||
|
# Enable NetworkManager at boot so the installed system has networking on first login.
|
||||||
systemctl enable NetworkManager
|
systemctl enable NetworkManager
|
||||||
|
|
||||||
# Populate /etc/skel before user creation so useradd -m copies everything
|
# 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 \
|
git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git /etc/skel/Dotfiles \
|
||||||
|| echo "Warning: dotfiles clone failed — clone manually after first boot."
|
|| echo "Warning: dotfiles clone failed — clone manually after first boot."
|
||||||
|
|
||||||
# Seed /etc/skel with base shell dotfiles from the repo clone
|
# Create standard XDG user directories in skel so they are present in every new home.
|
||||||
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
|
|
||||||
|
|
||||||
mkdir -p /etc/skel/{Desktop,Documents,Downloads,Music,Pictures,Public,Templates,Videos}
|
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"
|
useradd -m -G wheel -s /bin/zsh "$USERNAME"
|
||||||
|
# chpasswd reads "user:pass" from stdin to set the password non-interactively.
|
||||||
echo "$USERNAME:$USERPASS" | chpasswd
|
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"
|
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
|
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
|
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
|
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
|
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
|
sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect microcode modconf kms consolefont block btrfs filesystems keyboard fsck)/' /etc/mkinitcpio.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# -P regenerates all installed kernel presets (more thorough than -p <kernel>).
|
||||||
mkinitcpio -P
|
mkinitcpio -P
|
||||||
|
|
||||||
# GRUB
|
# GRUB
|
||||||
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
|
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
|
||||||
if [[ "$ENABLE_FIDO_ROOT" == "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"
|
GRUB_CMDLINE="rd.luks.name=$ROOT_UUID=cryptroot rd.luks.options=fido2-device=auto root=/dev/mapper/cryptroot"
|
||||||
else
|
else
|
||||||
|
# Classic encrypt hook syntax: cryptdevice=UUID=<UUID>:<dm-name>.
|
||||||
GRUB_CMDLINE="cryptdevice=UUID=$ROOT_UUID:cryptroot root=/dev/mapper/cryptroot"
|
GRUB_CMDLINE="cryptdevice=UUID=$ROOT_UUID:cryptroot root=/dev/mapper/cryptroot"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
|
# Unencrypted btrfs: root= by UUID; rootflags selects the @ subvolume.
|
||||||
GRUB_CMDLINE="root=UUID=${ROOT_UUID} rootflags=subvol=@"
|
GRUB_CMDLINE="root=UUID=${ROOT_UUID} rootflags=subvol=@"
|
||||||
fi
|
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
|
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
|
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
|
grub-mkconfig -o /boot/grub/grub.cfg
|
||||||
|
|
||||||
# User login FIDO2 — directory + PAM only; key enrollment happens outside chroot
|
# User login FIDO2 — directory + PAM only; key enrollment happens outside chroot
|
||||||
if [[ "$ENABLE_FIDO_USER" == "YES" ]]; then
|
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"
|
mkdir -p "/home/$USERNAME/.config/Yubico"
|
||||||
chown "$USERNAME:$USERNAME" "/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
|
echo "auth required pam_u2f.so cue" >> /etc/pam.d/system-auth
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -401,10 +503,15 @@ if [[ "$ENABLE_FIDO_USER" == "YES" ]]; then
|
||||||
echo "Enrolling FIDO2 key for user login (outside chroot)..."
|
echo "Enrolling FIDO2 key for user login (outside chroot)..."
|
||||||
U2F_KEYFILE="/mnt/home/${USERNAME}/.config/Yubico/u2f_keys"
|
U2F_KEYFILE="/mnt/home/${USERNAME}/.config/Yubico/u2f_keys"
|
||||||
mkdir -p "/mnt/home/${USERNAME}/.config/Yubico"
|
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"
|
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")
|
_NEWUID=$(arch-chroot /mnt id -u "$USERNAME" 2>/dev/null || echo "1000")
|
||||||
_NEWGID=$(arch-chroot /mnt id -g "$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"
|
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"
|
chmod 600 "$U2F_KEYFILE"
|
||||||
echo "FIDO2 key enrolled for $USERNAME."
|
echo "FIDO2 key enrolled for $USERNAME."
|
||||||
fi
|
fi
|
||||||
|
|
@ -413,30 +520,39 @@ fi
|
||||||
# DOTFILES SETUP (in-chroot, optional)
|
# DOTFILES SETUP (in-chroot, optional)
|
||||||
############################################
|
############################################
|
||||||
if $AF_MODE; then
|
if $AF_MODE; then
|
||||||
|
# RUN_TUI was already populated from the answerfile earlier.
|
||||||
_DO_TUI="${RUN_TUI}"
|
_DO_TUI="${RUN_TUI}"
|
||||||
else
|
else
|
||||||
read -rp "Run dotfiles TUI setup inside chroot now? [YES/no]: " _TUI_IN
|
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="${_TUI_IN:-YES}"
|
||||||
[[ "${_TUI_IN^^}" == "YES" ]] && _DO_TUI="YES" || _DO_TUI="NO"
|
[[ "${_TUI_IN^^}" == "YES" ]] && _DO_TUI="YES" || _DO_TUI="NO"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${_DO_TUI^^}" == "YES" ]]; then
|
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" \
|
echo "${USERNAME} ALL=(ALL) NOPASSWD: ALL" \
|
||||||
| arch-chroot /mnt tee /etc/sudoers.d/99-setup-nopasswd > /dev/null
|
| arch-chroot /mnt tee /etc/sudoers.d/99-setup-nopasswd > /dev/null
|
||||||
|
|
||||||
echo "Running simple-install.sh as ${USERNAME} inside chroot..."
|
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}" -- \
|
arch-chroot /mnt runuser -u "${USERNAME}" -- \
|
||||||
bash "/home/${USERNAME}/Dotfiles/setup/simple-install.sh" \
|
bash "/home/${USERNAME}/Dotfiles/setup/simple-install.sh" \
|
||||||
|| echo "Warning: tui-install exited with errors — check ~/dotfiles-install.log in the new system."
|
|| 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
|
arch-chroot /mnt rm -f /etc/sudoers.d/99-setup-nopasswd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Remove answerfile from new system after setup completes
|
# 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
|
if $AF_MODE && [[ -f /mnt/answerfile.json ]]; then
|
||||||
rm -f /mnt/answerfile.json
|
rm -f /mnt/answerfile.json
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Copy the install log into /boot so it is readable before the first login.
|
||||||
cp "$LOGFILE" /mnt/boot/ 2>/dev/null || true
|
cp "$LOGFILE" /mnt/boot/ 2>/dev/null || true
|
||||||
|
|
||||||
echo "Installation complete! Log saved to /mnt/boot/$(basename "$LOGFILE")"
|
echo "Installation complete! Log saved to /mnt/boot/$(basename "$LOGFILE")"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,27 @@
|
||||||
#!/usr/bin/env bash
|
#!/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]
|
# bash build.sh [--preconf [FILE]] [--netboot-url URL] [OUT_DIR]
|
||||||
#
|
#
|
||||||
# --preconf Embed ~/answerfile.json into the ISO at /answerfile.json
|
# --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
|
# --netboot-url URL Base URL where netboot artifacts will be served
|
||||||
# (generates m-archy-netboot.ipxe in OUT_DIR)
|
# (generates m-archy-netboot.ipxe in OUT_DIR)
|
||||||
# OUT_DIR Output directory (default: ~/m-archy-out, or $OUT_DIR env)
|
# OUT_DIR Output directory (default: ~/m-archy-out, or $OUT_DIR env)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
set -euo pipefail
|
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)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
DOTFILES_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
DOTFILES_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
|
||||||
# ── Argument parsing ───────────────────────────────────────────────────────────
|
# ── 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=""
|
PRECONF_FILE=""
|
||||||
NETBOOT_URL=""
|
NETBOOT_URL=""
|
||||||
OUT_ARG=""
|
OUT_ARG=""
|
||||||
|
|
@ -23,7 +54,8 @@ OUT_ARG=""
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--preconf)
|
--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
|
if [[ $# -gt 1 && "${2:0:1}" != "-" ]]; then
|
||||||
PRECONF_FILE="$2"; shift
|
PRECONF_FILE="$2"; shift
|
||||||
else
|
else
|
||||||
|
|
@ -32,6 +64,7 @@ while [[ $# -gt 0 ]]; do
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--preconf=*)
|
--preconf=*)
|
||||||
|
# Handle --preconf=/path/to/file syntax (strip prefix up to =)
|
||||||
PRECONF_FILE="${1#--preconf=}"
|
PRECONF_FILE="${1#--preconf=}"
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
|
@ -47,57 +80,104 @@ while [[ $# -gt 0 ]]; do
|
||||||
echo "Unknown flag: $1" >&2; exit 1
|
echo "Unknown flag: $1" >&2; exit 1
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
# Any non-flag argument is treated as the output directory
|
||||||
OUT_ARG="$1"; shift
|
OUT_ARG="$1"; shift
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
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}"
|
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}}"
|
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"
|
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"
|
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
|
if ! command -v mkarchiso &>/dev/null; then
|
||||||
echo "Installing archiso..."
|
echo "Installing archiso..."
|
||||||
sudo pacman -S --noconfirm archiso
|
sudo pacman -S --noconfirm archiso
|
||||||
fi
|
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; }
|
[[ -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
|
if [[ -n "$PRECONF_FILE" ]]; then
|
||||||
[[ -f "$PRECONF_FILE" ]] \
|
[[ -f "$PRECONF_FILE" ]] \
|
||||||
|| { echo "ERROR: answerfile not found: $PRECONF_FILE"; exit 1; }
|
|| { 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 \
|
command -v jq &>/dev/null \
|
||||||
&& jq empty "$PRECONF_FILE" \
|
&& jq empty "$PRECONF_FILE" \
|
||||||
|| echo "Warning: jq not available — skipping answerfile JSON validation"
|
|| echo "Warning: jq not available — skipping answerfile JSON validation"
|
||||||
echo "Answerfile to embed: $PRECONF_FILE"
|
echo "Answerfile to embed: $PRECONF_FILE"
|
||||||
fi
|
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"
|
rm -rf "$WORK_DIR"
|
||||||
mkdir -p "$WORK_DIR" "$OUT_DIR"
|
mkdir -p "$WORK_DIR" "$OUT_DIR"
|
||||||
|
|
||||||
|
# ── Assemble the profile from releng + M-Archy overlay ───────────────────────
|
||||||
echo "Copying releng base profile..."
|
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"
|
cp -r "$RELENG" "$PROFILE"
|
||||||
|
|
||||||
echo "Applying M-Archy overlay..."
|
echo "Applying M-Archy overlay..."
|
||||||
|
# Merge our custom airootfs overlay ON TOP of the releng copy.
|
||||||
|
# Files in our overlay replace or extend the releng defaults.
|
||||||
|
# The trailing /. ensures we copy the directory contents, not the directory itself.
|
||||||
cp -r "$SCRIPT_DIR/overlay/airootfs/." "$PROFILE/airootfs/"
|
cp -r "$SCRIPT_DIR/overlay/airootfs/." "$PROFILE/airootfs/"
|
||||||
|
|
||||||
echo "Replacing profiledef..."
|
echo "Replacing profiledef..."
|
||||||
|
# profiledef.sh controls ISO metadata (name, label, build modes, boot modes,
|
||||||
|
# compression settings, and file permissions in the final image).
|
||||||
|
# We replace the releng default entirely with our customized version.
|
||||||
cp "$SCRIPT_DIR/overlay/profiledef.sh" "$PROFILE/profiledef.sh"
|
cp "$SCRIPT_DIR/overlay/profiledef.sh" "$PROFILE/profiledef.sh"
|
||||||
|
|
||||||
echo "Adding extra packages..."
|
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
|
while IFS= read -r pkg || [[ -n "$pkg" ]]; do
|
||||||
[[ -z "$pkg" || "$pkg" == \#* ]] && continue
|
[[ -z "$pkg" || "$pkg" == \#* ]] && continue
|
||||||
grep -qxF "$pkg" "$PROFILE/packages.x86_64" || echo "$pkg" >> "$PROFILE/packages.x86_64"
|
grep -qxF "$pkg" "$PROFILE/packages.x86_64" || echo "$pkg" >> "$PROFILE/packages.x86_64"
|
||||||
done < "$SCRIPT_DIR/overlay/packages.extra"
|
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..."
|
echo "Embedding installer scripts..."
|
||||||
mkdir -p "$PROFILE/airootfs/root/installer"
|
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/"
|
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/"
|
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/"
|
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 \
|
chmod 755 \
|
||||||
"$PROFILE/airootfs/root/launch.sh" \
|
"$PROFILE/airootfs/root/launch.sh" \
|
||||||
"$PROFILE/airootfs/root/.automated_script.sh" \
|
"$PROFILE/airootfs/root/.automated_script.sh" \
|
||||||
|
|
@ -105,20 +185,36 @@ chmod 755 \
|
||||||
"$PROFILE/airootfs/root/installer/"*.sh
|
"$PROFILE/airootfs/root/installer/"*.sh
|
||||||
|
|
||||||
# ── Embed answerfile (--preconf) ───────────────────────────────────────────────
|
# ── 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
|
if [[ -n "$PRECONF_FILE" ]]; then
|
||||||
echo "Embedding answerfile: $PRECONF_FILE → /answerfile.json"
|
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"
|
install -m 644 "$PRECONF_FILE" "$PROFILE/airootfs/answerfile.json"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Building ISO (this may take a while)..."
|
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"
|
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 chmod -R 777 "$WORK_DIR" "$OUT_DIR"
|
||||||
sudo chown -R "$(id -u):$(id -g)" "$WORK_DIR" "$OUT_DIR"
|
sudo chown -R "$(id -u):$(id -g)" "$WORK_DIR" "$OUT_DIR"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "Done."
|
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
|
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
|
if [[ -n "$PRECONF_FILE" ]]; then
|
||||||
echo "Answerfile embedded — automated install will activate on boot."
|
echo "Answerfile embedded — automated install will activate on boot."
|
||||||
else
|
else
|
||||||
|
|
@ -126,18 +222,26 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Netboot artifacts ──────────────────────────────────────────────────────────
|
# ── 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)"
|
NETBOOT_TARBALL="$(ls "$OUT_DIR/"*-netboot-*.tar.gz 2>/dev/null | head -n1 || true)"
|
||||||
if [[ -n "$NETBOOT_TARBALL" ]]; then
|
if [[ -n "$NETBOOT_TARBALL" ]]; then
|
||||||
echo
|
echo
|
||||||
echo "Netboot artifact: $NETBOOT_TARBALL"
|
echo "Netboot artifact: $NETBOOT_TARBALL"
|
||||||
echo " Extract and serve its contents from an HTTP server, then boot via PXE."
|
echo " Extract and serve its contents from an HTTP server, then boot via PXE."
|
||||||
echo " Internal layout (relative to tarball root):"
|
echo " Internal layout (relative to tarball root):"
|
||||||
|
# Show the tarball contents indented for readability.
|
||||||
tar -tzf "$NETBOOT_TARBALL" | sed 's/^/ /'
|
tar -tzf "$NETBOOT_TARBALL" | sed 's/^/ /'
|
||||||
|
|
||||||
if [[ -n "$NETBOOT_URL" ]]; then
|
if [[ -n "$NETBOOT_URL" ]]; then
|
||||||
# Strip trailing slash for consistency
|
# Strip trailing slash so our URL concatenations are consistent.
|
||||||
BASE_URL="${NETBOOT_URL%/}"
|
BASE_URL="${NETBOOT_URL%/}"
|
||||||
IPXE_FILE="$OUT_DIR/m-archy-netboot.ipxe"
|
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
|
cat > "$IPXE_FILE" <<IPXE
|
||||||
#!ipxe
|
#!ipxe
|
||||||
# M-Archy Arch Linux Installer — PXE Boot
|
# M-Archy Arch Linux Installer — PXE Boot
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,48 @@
|
||||||
#!/usr/bin/env bash
|
#!/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
|
set -euo pipefail
|
||||||
|
|
||||||
ANSWERFILE="/answerfile.json"
|
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
|
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"
|
exec /root/launch.sh auto "$ANSWERFILE"
|
||||||
else
|
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
|
exec /root/launch.sh guided
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,26 @@
|
||||||
#!/usr/bin/env bash
|
#!/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]
|
# bash wds-deploy.sh --http-srv URL [OPTIONS] [OUT_DIR]
|
||||||
#
|
#
|
||||||
# --http-srv URL HTTP base URL where arch netboot files will be served
|
# --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
|
# --no-rebuild Skip the archiso build if a netboot tarball already exists
|
||||||
# OUT_DIR Output directory (default: ~/m-archy-out)
|
# 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\
|
# TFTP/ Copy contents to WDS TFTP root: C:\RemoteInstall\Boot\x64\
|
||||||
# HTTP/ Serve as HTTP root at the URL given to --http-srv
|
# 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-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
|
# 1. Serve HTTP/ over IIS/Nginx at the --http-srv URL
|
||||||
# 2. Extract wds-tftp.zip into C:\RemoteInstall\Boot\x64\
|
# 2. Extract wds-tftp.zip into C:\RemoteInstall\Boot\x64\
|
||||||
# 3. In WDS console → server Properties → Boot tab:
|
# 3. In WDS console → server Properties → Boot tab:
|
||||||
# Set "Default boot program" for x64 to: Boot\x64\pxelinux.0
|
# Set "Default boot program" for x64 to: Boot\x64\pxelinux.0
|
||||||
# 4. PXE-boot a client — the M-Archy menu appears
|
# 4. PXE-boot a client — the M-Archy menu appears
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
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"
|
SYSLINUX_BIOS="/usr/lib/syslinux/bios"
|
||||||
|
|
||||||
# ── Argument parsing ───────────────────────────────────────────────────────────
|
# ── Argument parsing ───────────────────────────────────────────────────────────
|
||||||
HTTP_SRV=""
|
HTTP_SRV="" # Required: base HTTP URL where airootfs.sfs is served
|
||||||
TFTP_PREFIX="m-archy"
|
TFTP_PREFIX="m-archy" # Subdirectory under TFTP root for kernel + initramfs
|
||||||
PRECONF_ARGS=()
|
PRECONF_ARGS=() # Forwarded to build.sh if --preconf was given
|
||||||
NO_REBUILD=0
|
NO_REBUILD=0 # 1 = skip build and reuse existing netboot tarball
|
||||||
OUT_ARG=""
|
OUT_ARG="" # Positional output directory override
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
|
@ -51,12 +75,14 @@ while [[ $# -gt 0 ]]; do
|
||||||
--tftp-prefix=*)
|
--tftp-prefix=*)
|
||||||
TFTP_PREFIX="${1#--tftp-prefix=}"; shift ;;
|
TFTP_PREFIX="${1#--tftp-prefix=}"; shift ;;
|
||||||
--preconf)
|
--preconf)
|
||||||
|
# Forward --preconf [optional-file] to build.sh verbatim.
|
||||||
if [[ $# -gt 1 && "${2:0:1}" != "-" ]]; then
|
if [[ $# -gt 1 && "${2:0:1}" != "-" ]]; then
|
||||||
PRECONF_ARGS=(--preconf "$2"); shift 2
|
PRECONF_ARGS=(--preconf "$2"); shift 2
|
||||||
else
|
else
|
||||||
PRECONF_ARGS=(--preconf); shift
|
PRECONF_ARGS=(--preconf); shift
|
||||||
fi ;;
|
fi ;;
|
||||||
--preconf=*)
|
--preconf=*)
|
||||||
|
# Forward --preconf=file to build.sh verbatim.
|
||||||
PRECONF_ARGS=("$1"); shift ;;
|
PRECONF_ARGS=("$1"); shift ;;
|
||||||
--no-rebuild)
|
--no-rebuild)
|
||||||
NO_REBUILD=1; shift ;;
|
NO_REBUILD=1; shift ;;
|
||||||
|
|
@ -68,9 +94,13 @@ while [[ $# -gt 0 ]]; do
|
||||||
done
|
done
|
||||||
|
|
||||||
OUT_DIR="${OUT_ARG:-${OUT_DIR:-$HOME/m-archy-out}}"
|
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"
|
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
|
if [[ -z "$HTTP_SRV" ]]; then
|
||||||
echo "ERROR: --http-srv <URL> is required." >&2
|
echo "ERROR: --http-srv <URL> is required." >&2
|
||||||
echo " The Arch initrd fetches airootfs.sfs over HTTP at boot." >&2
|
echo " The Arch initrd fetches airootfs.sfs over HTTP at boot." >&2
|
||||||
|
|
@ -78,26 +108,36 @@ if [[ -z "$HTTP_SRV" ]]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
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 ──────────────────────────────────────────────
|
# ── 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
|
if [[ ! -f "$SYSLINUX_BIOS/pxelinux.0" ]]; then
|
||||||
echo "syslinux not found — installing..."
|
echo "syslinux not found — installing..."
|
||||||
sudo pacman -S --noconfirm syslinux
|
sudo pacman -S --noconfirm syslinux
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Second check after attempted install — exit loudly if still missing.
|
||||||
if [[ ! -f "$SYSLINUX_BIOS/pxelinux.0" ]]; then
|
if [[ ! -f "$SYSLINUX_BIOS/pxelinux.0" ]]; then
|
||||||
echo "ERROR: $SYSLINUX_BIOS/pxelinux.0 still not found after install." >&2
|
echo "ERROR: $SYSLINUX_BIOS/pxelinux.0 still not found after install." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Build ISO + netboot tarball (unless --no-rebuild) ─────────────────────────
|
# ── 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)"
|
NETBOOT_TARBALL="$(ls "$OUT_DIR/"*-netboot-*.tar.gz 2>/dev/null | head -n1 || true)"
|
||||||
|
|
||||||
if [[ "$NO_REBUILD" -eq 1 && -n "$NETBOOT_TARBALL" ]]; then
|
if [[ "$NO_REBUILD" -eq 1 && -n "$NETBOOT_TARBALL" ]]; then
|
||||||
echo "Skipping build — using existing tarball: $(basename "$NETBOOT_TARBALL")"
|
echo "Skipping build — using existing tarball: $(basename "$NETBOOT_TARBALL")"
|
||||||
else
|
else
|
||||||
echo "Building archiso (this may take a while)..."
|
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"
|
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)"
|
NETBOOT_TARBALL="$(ls "$OUT_DIR/"*-netboot-*.tar.gz 2>/dev/null | head -n1 || true)"
|
||||||
fi
|
fi
|
||||||
|
|
@ -108,12 +148,17 @@ fi
|
||||||
echo "Using netboot tarball: $(basename "$NETBOOT_TARBALL")"
|
echo "Using netboot tarball: $(basename "$NETBOOT_TARBALL")"
|
||||||
|
|
||||||
# ── Extract 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"
|
EXTRACT_DIR="$OUT_DIR/.netboot-extracted"
|
||||||
rm -rf "$EXTRACT_DIR"
|
rm -rf "$EXTRACT_DIR"
|
||||||
mkdir -p "$EXTRACT_DIR"
|
mkdir -p "$EXTRACT_DIR"
|
||||||
echo "Extracting netboot tarball..."
|
echo "Extracting netboot tarball..."
|
||||||
tar -xzf "$NETBOOT_TARBALL" -C "$EXTRACT_DIR"
|
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)"
|
VMLINUZ="$(find "$EXTRACT_DIR" -name 'vmlinuz-linux' | head -n1 || true)"
|
||||||
INITRAMFS="$(find "$EXTRACT_DIR" -name 'initramfs-linux.img' | 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)"
|
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):
|
# TFTP root layout (mirrors what WDS serves via TFTP):
|
||||||
# pxelinux.0 PXELinux bootloader
|
# pxelinux.0 PXELinux bootloader
|
||||||
# ldlinux.c32 required by pxelinux.0
|
# ldlinux.c32 required by pxelinux.0
|
||||||
# menu.c32 + libcom32.c32 + libutil.c32 text boot menu
|
# menu.c32 + libcom32.c32 + libutil.c32 text boot menu modules
|
||||||
# pxelinux.cfg/default boot menu config
|
# pxelinux.cfg/default boot menu configuration
|
||||||
# <tftp-prefix>/arch/boot/x86_64/vmlinuz-linux
|
# <tftp-prefix>/arch/boot/x86_64/vmlinuz-linux Linux kernel
|
||||||
# <tftp-prefix>/arch/boot/x86_64/initramfs-linux.img
|
# <tftp-prefix>/arch/boot/x86_64/initramfs-linux.img initrd (with archiso hooks)
|
||||||
#
|
#
|
||||||
# HTTP root layout (served at $HTTP_SRV/ over IIS/Nginx/Apache):
|
# 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 the squashfs root filesystem fetched at boot
|
||||||
# arch/x86_64/airootfs.sfs.sha512
|
# arch/x86_64/airootfs.sfs.sha512 integrity checksum
|
||||||
# arch/pkglist.x86_64.txt (and any other netboot files)
|
# 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"
|
TFTP_ROOT="$WDS_DIR/TFTP"
|
||||||
HTTP_ROOT="$WDS_DIR/HTTP"
|
HTTP_ROOT="$WDS_DIR/HTTP"
|
||||||
|
|
@ -147,6 +199,11 @@ mkdir -p \
|
||||||
"$HTTP_ROOT"
|
"$HTTP_ROOT"
|
||||||
|
|
||||||
# ── Copy syslinux modules ─────────────────────────────────────────────────────
|
# ── 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..."
|
echo "Copying syslinux PXELinux modules..."
|
||||||
REQUIRED_MODS=(pxelinux.0 ldlinux.c32 menu.c32 libcom32.c32 libutil.c32)
|
REQUIRED_MODS=(pxelinux.0 ldlinux.c32 menu.c32 libcom32.c32 libutil.c32)
|
||||||
MISSING_MODS=()
|
MISSING_MODS=()
|
||||||
|
|
@ -155,6 +212,8 @@ for mod in "${REQUIRED_MODS[@]}"; do
|
||||||
if [[ -f "$SYSLINUX_BIOS/$mod" ]]; then
|
if [[ -f "$SYSLINUX_BIOS/$mod" ]]; then
|
||||||
cp "$SYSLINUX_BIOS/$mod" "$TFTP_ROOT/"
|
cp "$SYSLINUX_BIOS/$mod" "$TFTP_ROOT/"
|
||||||
else
|
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")
|
MISSING_MODS+=("$mod")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
@ -164,23 +223,32 @@ if [[ ${#MISSING_MODS[@]} -gt 0 ]]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Copy kernel and initramfs ─────────────────────────────────────────────────
|
# ── 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..."
|
echo "Copying kernel and initramfs..."
|
||||||
cp "$VMLINUZ" "$TFTP_ROOT/$TFTP_PREFIX/arch/boot/x86_64/vmlinuz-linux"
|
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"
|
cp "$INITRAMFS" "$TFTP_ROOT/$TFTP_PREFIX/arch/boot/x86_64/initramfs-linux.img"
|
||||||
|
|
||||||
# ── Copy HTTP content (airootfs squashfs and supporting files) ────────────────
|
# ── 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)..."
|
echo "Copying HTTP content (airootfs + supporting files)..."
|
||||||
cp -r "$ARCH_DIR" "$HTTP_ROOT/arch"
|
cp -r "$ARCH_DIR" "$HTTP_ROOT/arch"
|
||||||
|
|
||||||
# ── Generate pxelinux.cfg/default ────────────────────────────────────────────
|
# ── 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..."
|
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"
|
KERNEL_PATH="${TFTP_PREFIX}/arch/boot/x86_64/vmlinuz-linux"
|
||||||
INITRD_PATH="${TFTP_PREFIX}/arch/boot/x86_64/initramfs-linux.img"
|
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
|
# archiso kernel parameters:
|
||||||
# that contains the x86_64/ squashfs tree
|
# 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"
|
APPEND_BASE="initrd=${INITRD_PATH} archiso_http_srv=${HTTP_SRV}/ archisobasedir=arch ip=dhcp"
|
||||||
|
|
||||||
cat > "$TFTP_ROOT/pxelinux.cfg/default" <<PXECFG
|
cat > "$TFTP_ROOT/pxelinux.cfg/default" <<PXECFG
|
||||||
|
|
@ -212,9 +280,12 @@ LABEL local
|
||||||
PXECFG
|
PXECFG
|
||||||
|
|
||||||
# ── Zip TFTP directory for easy transfer to Windows Server ────────────────────
|
# ── 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"
|
ZIP_FILE="$WDS_DIR/wds-tftp.zip"
|
||||||
echo "Creating wds-tftp.zip..."
|
echo "Creating wds-tftp.zip..."
|
||||||
if command -v zip &>/dev/null; then
|
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" .)
|
(cd "$TFTP_ROOT" && zip -r "$ZIP_FILE" .)
|
||||||
echo "Created: $ZIP_FILE"
|
echo "Created: $ZIP_FILE"
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,71 @@
|
||||||
#!/bin/bash
|
#!/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).
|
# Verifies installed packages come from the expected source (pacman/AUR/flatpak).
|
||||||
# Reports missing packages, wrong-source installs, and unexpected foreign packages.
|
# Reports missing packages, wrong-source installs, and unexpected foreign packages.
|
||||||
# Pass --fix / -f to automatically reinstall packages from the correct source.
|
# Pass --fix / -f to automatically reinstall packages from the correct source.
|
||||||
set -euo pipefail
|
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"
|
RED="\e[31m"; YELLOW="\e[33m"; GREEN="\e[32m"; CYAN="\e[36m"; BOLD="\e[1m"; RESET="\e[0m"
|
||||||
|
|
||||||
ok() { echo -e " ${GREEN}✔${RESET} $1"; }
|
ok() { echo -e " ${GREEN}✔${RESET} $1"; } # Green checkmark: package is correct
|
||||||
warn() { echo -e " ${YELLOW}⚠${RESET} $1"; }
|
warn() { echo -e " ${YELLOW}⚠${RESET} $1"; } # Yellow warning: non-critical issue
|
||||||
err() { echo -e " ${RED}✘${RESET} $1"; }
|
err() { echo -e " ${RED}✘${RESET} $1"; } # Red X: package missing or wrong
|
||||||
hdr() { echo -e "\n${BOLD}${CYAN}$1${RESET}"; }
|
hdr() { echo -e "\n${BOLD}${CYAN}$1${RESET}"; } # Bold cyan section header
|
||||||
fix() { echo -e " ${CYAN}↺${RESET} $1"; }
|
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
|
FIX=0
|
||||||
[[ "${1:-}" == "--fix" || "${1:-}" == "-f" ]] && FIX=1
|
[[ "${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
|
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_OFFICIAL=() # expected from pacman, installed from AUR
|
||||||
WRONG_SOURCE_AUR=() # expected from AUR, installed from official
|
WRONG_SOURCE_AUR=() # expected from AUR, installed from official
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Expected package sources — keep in sync with setup/modules/
|
# 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=(
|
PACMAN_PKGS=(
|
||||||
# core-packages.sh
|
# core-packages.sh
|
||||||
7zip arch-install-scripts atftp atool
|
7zip arch-install-scripts atftp atool
|
||||||
|
|
@ -64,33 +105,43 @@ PACMAN_PKGS=(
|
||||||
kew
|
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=(
|
AUR_PKGS=(
|
||||||
# hyprland.sh AUR portion
|
# hyprland.sh AUR portion (not in official repos)
|
||||||
hyprland-workspaces vicinae-bin bluetuith wvkbd iwmenu
|
hyprland-workspaces vicinae-bin bluetuith wvkbd iwmenu
|
||||||
walker-bin ulauncher bzmenu wofi-calc bri chamel
|
walker-bin ulauncher bzmenu wofi-calc bri chamel
|
||||||
# optional apps
|
# optional apps (only installed if selected in TUI)
|
||||||
pamtester
|
pamtester # PAM authentication tester
|
||||||
pinta
|
pinta # Simple image editor (paint.net-like)
|
||||||
localsend
|
localsend # LAN file transfer
|
||||||
vesktop
|
vesktop # Discord with Vencord themes
|
||||||
onlyoffice-bin
|
onlyoffice-bin # Office suite (pre-built binary from AUR)
|
||||||
vintagestory
|
vintagestory # Survival game
|
||||||
wprs-git
|
wprs-git # Wayland proxy (git version)
|
||||||
zfs-dkms
|
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=(
|
FLATPAK_PKGS=(
|
||||||
org.prismlauncher.PrismLauncher
|
org.prismlauncher.PrismLauncher # Minecraft launcher
|
||||||
)
|
)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Snapshot current state
|
# 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)
|
ALL_INSTALLED=$(pacman -Qq 2>/dev/null) # All installed packages (name only)
|
||||||
FOREIGN=$(pacman -Qmq 2>/dev/null) # AUR / manually installed
|
FOREIGN=$(pacman -Qmq 2>/dev/null) # AUR / manually installed (foreign) packages
|
||||||
OFFICIAL=$(pacman -Qnq 2>/dev/null) # in a sync repo
|
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_installed() { echo "$ALL_INSTALLED" | grep -qx "$1"; }
|
||||||
is_from_official() { echo "$OFFICIAL" | grep -qx "$1"; }
|
is_from_official() { echo "$OFFICIAL" | grep -qx "$1"; }
|
||||||
is_from_aur() { echo "$FOREIGN" | 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
|
# 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)"
|
hdr "1/3 Official-repo packages (pacman)"
|
||||||
|
|
||||||
|
|
@ -105,6 +160,8 @@ for pkg in "${PACMAN_PKGS[@]}"; do
|
||||||
if ! is_installed "$pkg"; then
|
if ! is_installed "$pkg"; then
|
||||||
err "$pkg — NOT INSTALLED"; flag
|
err "$pkg — NOT INSTALLED"; flag
|
||||||
elif is_from_aur "$pkg"; then
|
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
|
warn "$pkg — installed, but from AUR instead of official repo"; flag
|
||||||
WRONG_SOURCE_OFFICIAL+=("$pkg")
|
WRONG_SOURCE_OFFICIAL+=("$pkg")
|
||||||
else
|
else
|
||||||
|
|
@ -115,13 +172,21 @@ done
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 2. Check AUR packages
|
# 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)"
|
hdr "2/3 AUR packages (yay)"
|
||||||
|
|
||||||
for pkg in "${AUR_PKGS[@]}"; do
|
for pkg in "${AUR_PKGS[@]}"; do
|
||||||
if ! is_installed "$pkg"; then
|
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)"
|
warn "$pkg — not installed (optional — may be intentional)"
|
||||||
elif is_from_official "$pkg"; then
|
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
|
warn "$pkg — installed from official repo, not AUR (may need AUR build for correct version)"; flag
|
||||||
WRONG_SOURCE_AUR+=("$pkg")
|
WRONG_SOURCE_AUR+=("$pkg")
|
||||||
else
|
else
|
||||||
|
|
@ -132,10 +197,13 @@ done
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 3. Check flatpak packages
|
# 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"
|
hdr "3/3 Flatpak packages"
|
||||||
|
|
||||||
if command -v flatpak &>/dev/null; then
|
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)
|
FLATPAK_INSTALLED=$(flatpak list --app --columns=application 2>/dev/null)
|
||||||
for pkg in "${FLATPAK_PKGS[@]}"; do
|
for pkg in "${FLATPAK_PKGS[@]}"; do
|
||||||
if echo "$FLATPAK_INSTALLED" | grep -qx "$pkg"; then
|
if echo "$FLATPAK_INSTALLED" | grep -qx "$pkg"; then
|
||||||
|
|
@ -151,11 +219,16 @@ fi
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 4. Flag unexpected foreign (AUR) packages not in our lists
|
# 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"
|
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[@]}")
|
AUR_SET=$(printf "%s\n" "${AUR_PKGS[@]}")
|
||||||
while IFS= read -r pkg; do
|
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
|
if ! echo "$AUR_SET" | grep -qx "$pkg"; then
|
||||||
echo -e " ${CYAN}?${RESET} $pkg — foreign package not in setup scripts"
|
echo -e " ${CYAN}?${RESET} $pkg — foreign package not in setup scripts"
|
||||||
fi
|
fi
|
||||||
|
|
@ -164,6 +237,8 @@ done <<< "$FOREIGN"
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Fix: reinstall wrong-source packages
|
# 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
|
FIXED=0
|
||||||
|
|
||||||
|
|
@ -172,19 +247,23 @@ if [ "$FIX" -eq 1 ]; then
|
||||||
hdr "Reinstalling wrong-source packages"
|
hdr "Reinstalling wrong-source packages"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Reinstall from official repo (packages that were incorrectly from AUR)
|
||||||
for pkg in "${WRONG_SOURCE_OFFICIAL[@]}"; do
|
for pkg in "${WRONG_SOURCE_OFFICIAL[@]}"; do
|
||||||
fix "Reinstalling $pkg from official repo (was AUR)..."
|
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
|
if sudo pacman -S --noconfirm "$pkg"; then
|
||||||
ok "$pkg reinstalled from official repo"
|
ok "$pkg reinstalled from official repo"
|
||||||
FIXED=$((FIXED + 1))
|
FIXED=$((FIXED + 1))
|
||||||
ISSUES=$((ISSUES - 1))
|
ISSUES=$((ISSUES - 1)) # Remove from issue count since it's now fixed
|
||||||
else
|
else
|
||||||
err "$pkg reinstall failed"
|
err "$pkg reinstall failed"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Reinstall from AUR (packages that were incorrectly from official repos)
|
||||||
for pkg in "${WRONG_SOURCE_AUR[@]}"; do
|
for pkg in "${WRONG_SOURCE_AUR[@]}"; do
|
||||||
fix "Reinstalling $pkg from AUR (was official repo)..."
|
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
|
if yay -S --aur --noconfirm "$pkg"; then
|
||||||
ok "$pkg reinstalled from AUR"
|
ok "$pkg reinstalled from AUR"
|
||||||
FIXED=$((FIXED + 1))
|
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}"
|
echo -e "${GREEN}${BOLD}All checks passed — no source mismatches or missing required packages.${RESET}"
|
||||||
else
|
else
|
||||||
echo -e "${RED}${BOLD}${ISSUES} issue(s) found — review items marked ✘ or ⚠ above.${RESET}"
|
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 ] && \
|
[ "$FIX" -eq 0 ] && [ $((${#WRONG_SOURCE_OFFICIAL[@]} + ${#WRONG_SOURCE_AUR[@]})) -gt 0 ] && \
|
||||||
echo -e "${YELLOW} Run with --fix to automatically reinstall wrong-source packages.${RESET}"
|
echo -e "${YELLOW} Run with --fix to automatically reinstall wrong-source packages.${RESET}"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,79 @@
|
||||||
#!/bin/bash
|
#!/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
|
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"
|
LOG="$HOME/dotfiles-install.log"
|
||||||
|
|
||||||
|
# Truncate the log file at the start of each run (> with no input empties it)
|
||||||
> "$LOG"
|
> "$LOG"
|
||||||
|
|
||||||
|
# Stamp the log with a timestamp for traceability
|
||||||
printf "Dotfiles install: %s\n" "$(date)" >> "$LOG"
|
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
|
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"
|
echo "Running Core installation Scripts"
|
||||||
bash ~/Dotfiles/setup/modules/package-managers.sh
|
bash ~/Dotfiles/setup/modules/package-managers.sh
|
||||||
bash ~/Dotfiles/setup/modules/core-packages.sh
|
bash ~/Dotfiles/setup/modules/core-packages.sh
|
||||||
bash ~/Dotfiles/setup/modules/core.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"
|
echo "Running Shell config deployment Script"
|
||||||
bash ~/Dotfiles/setup/modules/shell-setup.sh
|
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
|
read -n1 -p "what DE to install? [hyprland,sway,none]" doit
|
||||||
case $doit in
|
case $doit in
|
||||||
|
# HyprLua is the primary/recommended DE — Hyprland configured via Lua scripts
|
||||||
hyprland) bash ~/Dotfiles/setup/modules/Desktop-Environments/hyprland.sh ;;
|
hyprland) bash ~/Dotfiles/setup/modules/Desktop-Environments/hyprland.sh ;;
|
||||||
|
# Sway — Wayland tiling compositor, i3-compatible
|
||||||
sway) bash ~/Dotfiles/setup/modules/Desktop-Environments/sway.sh ;;
|
sway) bash ~/Dotfiles/setup/modules/Desktop-Environments/sway.sh ;;
|
||||||
|
# Skip DE entirely (for headless/server setups)
|
||||||
none) echo "Skipping DE installation" ;;
|
none) echo "Skipping DE installation" ;;
|
||||||
|
# Catch-all for invalid input
|
||||||
*) echo "please choose a desktop environment to install" ;;
|
*) echo "please choose a desktop environment to install" ;;
|
||||||
esac
|
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:
|
# Optional apps — uncomment what you want:
|
||||||
# bash ~/Dotfiles/setup/modules/optional-Modules/apps/steam.sh
|
# bash ~/Dotfiles/setup/modules/optional-Modules/apps/steam.sh
|
||||||
# bash ~/Dotfiles/setup/modules/optional-Modules/apps/vesktop.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/zfs.sh
|
||||||
# bash ~/Dotfiles/setup/modules/optional-Modules/wprs.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"
|
printf "\nDone. Log: %s\n" "$LOG"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,55 @@
|
||||||
#!/bin/bash
|
#!/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
|
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"
|
source "$(dirname "${BASH_SOURCE[0]}")/../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing COSMIC desktop..."
|
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 \
|
sudo pacman -S --noconfirm --needed \
|
||||||
cosmic \
|
cosmic \
|
||||||
pipewire wireplumber pipewire-alsa pipewire-jack pipewire-pulse \
|
pipewire wireplumber pipewire-alsa pipewire-jack pipewire-pulse \
|
||||||
|
|
@ -11,14 +58,30 @@ sudo pacman -S --noconfirm --needed \
|
||||||
flatpak
|
flatpak
|
||||||
|
|
||||||
log "Enabling services..."
|
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
|
# cosmic-greeter is COSMIC's own display manager; fall back to sddm if absent
|
||||||
if pacman -Qi cosmic-greeter &>/dev/null; then
|
if pacman -Qi cosmic-greeter &>/dev/null; then
|
||||||
sudo systemctl enable cosmic-greeter.service
|
sudo systemctl enable cosmic-greeter.service
|
||||||
else
|
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 pacman -S --noconfirm --needed sddm
|
||||||
sudo systemctl enable sddm.service
|
sudo systemctl enable sddm.service
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Enable NetworkManager so the network is managed on every boot.
|
||||||
sudo systemctl enable NetworkManager.service
|
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
|
sudo systemctl enable bluetooth.service
|
||||||
|
|
||||||
log "COSMIC installation complete. Reboot to start."
|
log "COSMIC installation complete. Reboot to start."
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,56 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Load shared logging helpers (log, warn, err) from the setup library.
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing GNOME desktop..."
|
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 \
|
sudo pacman -S --noconfirm --needed \
|
||||||
gnome \
|
gnome \
|
||||||
gnome-tweaks \
|
gnome-tweaks \
|
||||||
|
|
@ -12,7 +60,12 @@ sudo pacman -S --noconfirm --needed \
|
||||||
flatpak
|
flatpak
|
||||||
|
|
||||||
log "Enabling services..."
|
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
|
sudo systemctl enable gdm.service
|
||||||
|
|
||||||
|
# Start NetworkManager on boot so connections are available before login.
|
||||||
sudo systemctl enable NetworkManager.service
|
sudo systemctl enable NetworkManager.service
|
||||||
|
|
||||||
log "GNOME installation complete. Reboot to start."
|
log "GNOME installation complete. Reboot to start."
|
||||||
|
|
|
||||||
|
|
@ -1,144 +1,405 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -e
|
||||||
|
|
||||||
|
# Load shared logging helpers (log, warn, err) from the setup library.
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../lib/logging.sh"
|
||||||
|
|
||||||
log "Starting Hyprland installer (legacy — hyprlang config)..."
|
log "Starting Hyprland installer (legacy — hyprlang config)..."
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 1. Update system and install Flatpak
|
# 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..."
|
log "Updating system and installing Flatpak..."
|
||||||
sudo pacman -Syu --noconfirm --needed flatpak
|
sudo pacman -Syu --noconfirm --needed flatpak
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 2. Install required packages
|
# 2. Install required packages
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
log "Installing required packages..."
|
log "Installing required packages..."
|
||||||
sudo pacman -Syu --noconfirm --needed \
|
sudo pacman -Syu --noconfirm --needed \
|
||||||
hyprland hyprcursor wl-clipboard hyprpaper hyprlock wofi kitty dunst \
|
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 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 \
|
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 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 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 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 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 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 \
|
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 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 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 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 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 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 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 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
|
# 3. Enable essential services
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
log "Enabling essential services..."
|
log "Enabling essential services..."
|
||||||
|
|
||||||
|
# NetworkManager must be active on boot for network connectivity.
|
||||||
sudo systemctl enable NetworkManager.service
|
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
|
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
|
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
|
sudo systemctl enable udisks2.service
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 4. Install AUR packages
|
# 4. Install AUR packages
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
log "Installing 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
|
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 \
|
yay -Syu --answerdiff None --answerclean All --noconfirm \
|
||||||
hyprland-workspaces vicinae-bin bluetuith wvkbd iwmenu pinta \
|
hyprland-workspaces vicinae-bin bluetuith wvkbd iwmenu pinta \
|
||||||
walker-bin ulauncher bzmenu udiskie \
|
walker-bin ulauncher bzmenu udiskie \
|
||||||
wofi-calc bri chamel
|
wofi-calc bri chamel
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 5. EWW bar selection and compilation
|
# 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..."
|
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
|
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
|
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
|
case $doit in
|
||||||
|
# Notebook: copy the battery-aware layout
|
||||||
n|N) cp -rf ~/Dotfiles/desktopenvs/hyprland/eww/ ~/.config/ ;;
|
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 ;;
|
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 ;;
|
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." ;;
|
*) warn "No valid choice — skipping EWW copy. Run manually later." ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
log "Compiling EWW..."
|
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
|
mkdir -p ~/install-tmp
|
||||||
cd ~/install-tmp
|
cd ~/install-tmp
|
||||||
git clone https://github.com/elkowar/eww
|
git clone https://github.com/elkowar/eww
|
||||||
cd 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
|
cargo build --release --no-default-features --features=wayland
|
||||||
chmod +x target/release/eww
|
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/
|
sudo cp target/release/eww /usr/bin/
|
||||||
cd ~
|
cd ~ # Return home so subsequent relative-path steps work correctly
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 6. Theme and icon setup
|
# 6. Theme and icon setup
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
log "Installing themes and icons..."
|
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
|
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
|
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
|
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
|
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
|
sudo ln -sf /usr/bin/ksshaskpass /usr/lib/ssh/ssh-askpass
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 7. Cursor setup
|
# 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..."
|
log "Installing cursor theme..."
|
||||||
mkdir -p ~/.icons
|
mkdir -p ~/.icons
|
||||||
wget -O ~/install-tmp/Nordzy-cursors-lefthand.tar.gz \
|
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
|
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/
|
tar -zxf ~/install-tmp/Nordzy-cursors-lefthand.tar.gz -C ~/.icons/
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 8. Enable Bluetooth and wireless services
|
# 8. Enable Bluetooth and wireless services
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
log "Enabling 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
|
sudo systemctl enable bluez
|
||||||
|
# bluetooth.service: high-level service handling pairing and profiles
|
||||||
sudo systemctl enable bluetooth.service
|
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
|
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 update
|
||||||
# hyprpm add https://github.com/hyprwm/hyprland-plugins
|
# hyprpm add https://github.com/hyprwm/hyprland-plugins
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 10. Copy configs
|
# 10. Copy configs
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
log "Copying 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)
|
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
|
for cfg in "${CONFIGS[@]}"; do
|
||||||
rm -rf ~/.config/"$cfg"
|
rm -rf ~/.config/"$cfg"
|
||||||
cp -r ~/Dotfiles/desktopenvs/hyprland/"$cfg" ~/.config/
|
cp -r ~/Dotfiles/desktopenvs/hyprland/"$cfg" ~/.config/
|
||||||
done
|
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/
|
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
|
cp ~/Dotfiles/colors.conf ~/.config/colors.conf
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 11. Wallpaper and resources
|
# 11. Wallpaper and resources
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
log "Copying wallpaper and resources..."
|
log "Copying wallpaper and resources..."
|
||||||
mkdir -p ~/Pictures
|
mkdir -p ~/Pictures
|
||||||
|
|
||||||
|
# Firefox logo SVG used as the dock/launcher shortcut icon.
|
||||||
cp ~/Dotfiles/resources/fflogo.svg ~/Pictures/fflogo.svg
|
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
|
wget "https://cloud.abdelbaki.eu/apps/theming/image/background?v=15" -O ~/Pictures/background.jpg
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 12. Python venv for scripts
|
# 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..."
|
log "Setting up Python venv for scripts..."
|
||||||
python -m venv ~/.config/python-script
|
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
|
# 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..."
|
log "Applying Udiskie icon fix..."
|
||||||
PAPIRUS_DIR="/usr/share/icons/Papirus-Dark/status"
|
PAPIRUS_DIR="/usr/share/icons/Papirus-Dark/status"
|
||||||
HICOLOR_DIR="/usr/share/icons/hicolor/scalable/status"
|
HICOLOR_DIR="/usr/share/icons/hicolor/scalable/status"
|
||||||
if [ -d "$PAPIRUS_DIR" ]; then
|
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-checked.svg" "$HICOLOR_DIR/udiskie-checkbox-checked.svg"
|
||||||
sudo ln -sf "$PAPIRUS_DIR/checkbox-unchecked.svg" "$HICOLOR_DIR/udiskie-checkbox-unchecked.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
|
sudo gtk-update-icon-cache -f -t /usr/share/icons/hicolor
|
||||||
else
|
else
|
||||||
warn "Papirus-Dark not found — skipping udiskie icon fix."
|
warn "Papirus-Dark not found — skipping udiskie icon fix."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 14. Enable udiskie
|
# 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..."
|
log "Enabling udiskie service..."
|
||||||
sudo systemctl enable udiskie.service
|
sudo systemctl enable udiskie.service
|
||||||
sudo systemctl start udiskie.service
|
sudo systemctl start udiskie.service
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 15. Install config updater and theme script
|
# 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..."
|
log "Installing config updater and theme script..."
|
||||||
mkdir -p ~/.config/config-updater
|
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/updater.conf ~/.config/config-updater/updater.conf
|
||||||
ln -sf ~/Dotfiles/desktopenvs/hyprland/config-updater/update-configs.sh ~/update-configs.sh
|
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
|
cp ~/Dotfiles/apply-theme.sh ~/apply-theme.sh
|
||||||
chmod +x ~/apply-theme.sh
|
chmod +x ~/apply-theme.sh
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 16. WallRizz (run manually after login — requires a running desktop session)
|
# 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 \
|
# curl -sL "$(curl -s https://api.github.com/repos/5hubham5ingh/WallRizz/releases/latest \
|
||||||
# | grep -Po '"browser_download_url": "\K[^"]+' | grep WallRizz)" | tar -xz \
|
# | grep -Po '"browser_download_url": "\K[^"]+' | grep WallRizz)" | tar -xz \
|
||||||
# && sudo mv WallRizz /usr/bin/
|
# && sudo mv WallRizz /usr/bin/
|
||||||
|
|
|
||||||
|
|
@ -1,147 +1,407 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -e
|
||||||
|
|
||||||
|
# Load shared logging helpers (log, warn, err) from the setup library.
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../lib/logging.sh"
|
||||||
|
|
||||||
log "Starting HyprLua installer (Lua-based config)..."
|
log "Starting HyprLua installer (Lua-based config)..."
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 1. Update system and install Flatpak
|
# 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..."
|
log "Updating system and installing Flatpak..."
|
||||||
sudo pacman -Syu --noconfirm --needed flatpak
|
sudo pacman -Syu --noconfirm --needed flatpak
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 2. Install required packages
|
# 2. Install required packages
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
log "Installing required packages..."
|
log "Installing required packages..."
|
||||||
sudo pacman -Syu --noconfirm --needed \
|
sudo pacman -Syu --noconfirm --needed \
|
||||||
hyprland hyprcursor wl-clipboard hyprpaper hyprlock wofi kitty dunst \
|
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 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 \
|
python cmake meson cpio pkgconf ruby-pkg-config \
|
||||||
|
# Build toolchain required for EWW (Rust) and AUR package compilation
|
||||||
hyprsunset hypridle ksshaskpass \
|
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 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 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 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 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 \
|
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 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 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 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 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 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 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 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 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
|
# 3. Enable essential services
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
log "Enabling essential services..."
|
log "Enabling essential services..."
|
||||||
|
|
||||||
|
# NetworkManager: manages all network connections (wired, Wi-Fi, VPN).
|
||||||
sudo systemctl enable NetworkManager.service
|
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
|
sudo systemctl disable getty@tty1.service || true
|
||||||
|
|
||||||
|
# ly: TUI display manager that presents the login screen on tty1.
|
||||||
sudo systemctl enable ly@tty1.service
|
sudo systemctl enable ly@tty1.service
|
||||||
|
|
||||||
|
# udisks2: D-Bus block-device service required by udiskie for auto-mounting.
|
||||||
sudo systemctl enable udisks2.service
|
sudo systemctl enable udisks2.service
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 4. Install AUR packages
|
# 4. Install AUR packages
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
log "Installing AUR packages..."
|
log "Installing AUR packages..."
|
||||||
|
|
||||||
|
# Pin the stable Rust toolchain so EWW and AUR Rust packages compile cleanly.
|
||||||
rustup default stable
|
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 \
|
yay -Syu --answerdiff None --answerclean All --noconfirm --needed \
|
||||||
hyprland-workspaces vicinae-bin bluetuith wvkbd iwmenu pinta \
|
hyprland-workspaces vicinae-bin bluetuith wvkbd iwmenu pinta \
|
||||||
walker-bin ulauncher bzmenu udiskie \
|
walker-bin ulauncher bzmenu udiskie \
|
||||||
wofi-calc bri chamel hyprmoncfg
|
wofi-calc bri chamel hyprmoncfg
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 5. EWW bar selection and compilation
|
# 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..."
|
log "Setting up EWW bar..."
|
||||||
|
|
||||||
|
# Remove any existing EWW config to avoid leftover widget definitions.
|
||||||
rm -rf ~/.config/eww
|
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
|
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
|
case $doit in
|
||||||
|
# Notebook: battery-aware layout
|
||||||
n|N) cp -rf ~/Dotfiles/desktopenvs/hyprlua/eww/ ~/.config/ ;;
|
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 ;;
|
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
|
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
|
yay -S evdev-right-click-emulation
|
||||||
sudo systemctl enable --now evdev-rce.service ;;
|
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." ;;
|
*) warn "No valid choice — skipping EWW copy. Run manually later." ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
log "Compiling EWW..."
|
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
|
mkdir -p ~/install-tmp
|
||||||
cd ~/install-tmp
|
cd ~/install-tmp
|
||||||
git clone https://github.com/elkowar/eww
|
git clone https://github.com/elkowar/eww
|
||||||
cd 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
|
cargo build --release --no-default-features --features=wayland
|
||||||
chmod +x target/release/eww
|
chmod +x target/release/eww
|
||||||
|
# Install system-wide so autostart entries and scripts can invoke 'eww' directly.
|
||||||
sudo cp target/release/eww /usr/bin/
|
sudo cp target/release/eww /usr/bin/
|
||||||
cd ~
|
cd ~ # Return home so subsequent relative paths resolve correctly
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 6. Theme and icon setup
|
# 6. Theme and icon setup
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
log "Installing themes and icons..."
|
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
|
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
|
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
|
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
|
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
|
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
|
#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'
|
gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark'
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 7. Cursor setup
|
# 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..."
|
log "Installing cursor theme..."
|
||||||
mkdir -p ~/.icons
|
mkdir -p ~/.icons
|
||||||
wget -O ~/install-tmp/Nordzy-cursors-lefthand.tar.gz \
|
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
|
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/
|
tar -zxf ~/install-tmp/Nordzy-cursors-lefthand.tar.gz -C ~/.icons/
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 8. Enable Bluetooth and wireless services
|
# 8. Enable Bluetooth and wireless services
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
log "Enabling 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
|
sudo systemctl enable bluez
|
||||||
|
# bluetooth.service: handles pairing, profiles, and device reconnection
|
||||||
sudo systemctl enable bluetooth.service
|
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
|
sudo systemctl enable iwd.service
|
||||||
systemctl --user enable --now hyprmoncfgd
|
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 update
|
||||||
# hyprpm add https://github.com/hyprwm/hyprland-plugins
|
# hyprpm add https://github.com/hyprwm/hyprland-plugins
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 10. Copy configs
|
# 10. Copy configs
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
log "Copying 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)
|
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
|
for cfg in "${CONFIGS[@]}"; do
|
||||||
rm -rf ~/.config/"$cfg"
|
rm -rf ~/.config/"$cfg"
|
||||||
cp -r ~/Dotfiles/desktopenvs/hyprlua/"$cfg" ~/.config/
|
cp -r ~/Dotfiles/desktopenvs/hyprlua/"$cfg" ~/.config/
|
||||||
done
|
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
|
cp ~/Dotfiles/colors.conf ~/.config/colors.conf
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 11. Wallpaper and resources
|
# 11. Wallpaper and resources
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
log "Copying wallpaper and resources..."
|
log "Copying wallpaper and resources..."
|
||||||
mkdir -p ~/Pictures
|
mkdir -p ~/Pictures
|
||||||
|
|
||||||
|
# Firefox logo SVG for the dock/launcher shortcut icon.
|
||||||
cp ~/Dotfiles/resources/fflogo.svg ~/Pictures/fflogo.svg
|
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
|
wget "https://cloud.abdelbaki.eu/apps/theming/image/background?v=15" -O ~/Pictures/background.jpg
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 12. Python venv for scripts
|
# 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..."
|
log "Setting up Python venv for scripts..."
|
||||||
python -m venv ~/.config/python-script
|
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
|
# 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..."
|
log "Applying Udiskie icon fix..."
|
||||||
PAPIRUS_DIR="/usr/share/icons/Papirus-Dark/status"
|
PAPIRUS_DIR="/usr/share/icons/Papirus-Dark/status"
|
||||||
HICOLOR_DIR="/usr/share/icons/hicolor/scalable/status"
|
HICOLOR_DIR="/usr/share/icons/hicolor/scalable/status"
|
||||||
if [ -d "$PAPIRUS_DIR" ]; then
|
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-checked.svg" "$HICOLOR_DIR/udiskie-checkbox-checked.svg"
|
||||||
sudo ln -sf "$PAPIRUS_DIR/checkbox-unchecked.svg" "$HICOLOR_DIR/udiskie-checkbox-unchecked.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
|
sudo gtk-update-icon-cache -f -t /usr/share/icons/hicolor
|
||||||
else
|
else
|
||||||
warn "Papirus-Dark not found — skipping udiskie icon fix."
|
warn "Papirus-Dark not found — skipping udiskie icon fix."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 14. Enable udiskie
|
# 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..."
|
log "Enabling udiskie service..."
|
||||||
sudo systemctl enable udiskie.service
|
sudo systemctl enable udiskie.service
|
||||||
sudo systemctl start udiskie.service
|
sudo systemctl start udiskie.service
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# 15. Install config updater and theme script
|
# 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..."
|
log "Installing config updater and theme script..."
|
||||||
mkdir -p ~/.config/config-updater
|
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/updater.conf ~/.config/config-updater/updater.conf
|
||||||
ln -sf ~/Dotfiles/desktopenvs/hyprlua/config-updater/update-configs.sh ~/update-configs.sh
|
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
|
cp ~/Dotfiles/apply-theme.sh ~/apply-theme.sh
|
||||||
chmod +x ~/apply-theme.sh
|
chmod +x ~/apply-theme.sh
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,19 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# set -euo pipefail: abort on errors, unset vars, and pipeline failures.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../lib/logging.sh"
|
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..."
|
log "Installing KDE Plasma desktop..."
|
||||||
sudo pacman -S --noconfirm --needed \
|
sudo pacman -S --noconfirm --needed \
|
||||||
plasma-meta \
|
plasma-meta \
|
||||||
|
|
@ -16,6 +28,7 @@ sudo pacman -S --noconfirm --needed \
|
||||||
flatpak
|
flatpak
|
||||||
|
|
||||||
log "Enabling services..."
|
log "Enabling services..."
|
||||||
|
# sddm provides the graphical login screen; must be enabled before rebooting.
|
||||||
sudo systemctl enable sddm.service
|
sudo systemctl enable sddm.service
|
||||||
sudo systemctl enable NetworkManager.service
|
sudo systemctl enable NetworkManager.service
|
||||||
sudo systemctl enable bluetooth.service
|
sudo systemctl enable bluetooth.service
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,166 @@
|
||||||
#!/bin/bash
|
#!/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
|
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"
|
source "$(dirname "${BASH_SOURCE[0]}")/lib/logging.sh"
|
||||||
|
|
||||||
log "Installing core packages..."
|
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..."
|
log "Installing AUR packages..."
|
||||||
yay -S --aur --noconfirm --needed pamtester
|
yay -S --aur --noconfirm --needed pamtester
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,68 @@
|
||||||
#!/bin/bash
|
#!/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
|
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"
|
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..."
|
log "Enabling NetworkManager..."
|
||||||
sudo systemctl enable NetworkManager.service
|
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..."
|
log "Enabling cronie..."
|
||||||
sudo systemctl enable cronie.service
|
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..."
|
log "Deploying greetd config..."
|
||||||
sudo cp -f ~/Dotfiles/desktopenvs/hyprland/greetd-tuigreet/config.toml /etc/greetd/config.toml
|
sudo cp -f ~/Dotfiles/desktopenvs/hyprland/greetd-tuigreet/config.toml /etc/greetd/config.toml
|
||||||
sudo systemctl enable greetd.service
|
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..."
|
log "Enabling fail2ban..."
|
||||||
sudo systemctl enable fail2ban.service
|
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..."
|
log "Enabling udisks2..."
|
||||||
sudo systemctl enable udisks2.service
|
sudo systemctl enable udisks2.service
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,104 @@
|
||||||
#!/bin/bash
|
#!/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
|
# 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"
|
GREEN="\e[32m"
|
||||||
YELLOW="\e[33m"
|
YELLOW="\e[33m"
|
||||||
RED="\e[31m"
|
RED="\e[31m"
|
||||||
RESET="\e[0m"
|
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" "$*"; }
|
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" "$*"; }
|
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; }
|
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; }
|
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() {
|
ensure_flatpak() {
|
||||||
|
# Check if flatpak binary is available; install it via pacman if not
|
||||||
if ! command -v flatpak &>/dev/null; then
|
if ! command -v flatpak &>/dev/null; then
|
||||||
log "Installing flatpak..."
|
log "Installing flatpak..."
|
||||||
sudo pacman -S --noconfirm --needed flatpak
|
sudo pacman -S --noconfirm --needed flatpak
|
||||||
fi
|
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
|
if ! flatpak remotes 2>/dev/null | grep -q flathub; then
|
||||||
log "Adding Flathub remote..."
|
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
|
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||||
fi
|
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() {
|
apply_flatpak_theme() {
|
||||||
local app_id="$1"
|
local app_id="$1"
|
||||||
local theme_name="cyberqueer"
|
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 theme_src="$HOME/Dotfiles/gtk-themes/$theme_name"
|
||||||
local themes_dir="$HOME/.themes"
|
local themes_dir="$HOME/.themes"
|
||||||
|
|
||||||
|
# Guard: if the theme source doesn't exist, skip gracefully with a warning
|
||||||
if [[ ! -d "$theme_src" ]]; then
|
if [[ ! -d "$theme_src" ]]; then
|
||||||
warn "Cyberqueer theme not found at $theme_src — skipping Flatpak theme override."
|
warn "Cyberqueer theme not found at $theme_src — skipping Flatpak theme override."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Copy the theme into the user's ~/.themes/ so it's accessible
|
||||||
mkdir -p "$themes_dir"
|
mkdir -p "$themes_dir"
|
||||||
cp -r "$theme_src" "$themes_dir/$theme_name"
|
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"
|
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"
|
flatpak override --user --env=GTK_THEME="$theme_name" "$app_id"
|
||||||
|
|
||||||
log "Cyberqueer theme applied to $app_id."
|
log "Cyberqueer theme applied to $app_id."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,39 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -euo pipefail
|
||||||
|
# Load shared logging helpers (log, skip, warn) from the dotfiles lib
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
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)..."
|
log "Installing anti-malware tools (clamav, clamtk, rkhunter)..."
|
||||||
sudo pacman -S --noconfirm --needed \
|
sudo pacman -S --noconfirm --needed \
|
||||||
clamav clamtk rkhunter
|
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)..."
|
log "Installing chkrootkit (AUR)..."
|
||||||
yay -S --aur --noconfirm --needed chkrootkit
|
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
|
if [[ ! -f /var/lib/clamav/main.cvd ]] && [[ ! -f /var/lib/clamav/main.cld ]]; then
|
||||||
log "Running initial freshclam (virus database update)..."
|
log "Running initial freshclam (virus database update)..."
|
||||||
sudo freshclam
|
sudo freshclam
|
||||||
|
|
@ -17,14 +41,21 @@ else
|
||||||
skip "ClamAV database already present."
|
skip "ClamAV database already present."
|
||||||
fi
|
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
|
CRON_FILE=/etc/cron.d/freshclam
|
||||||
if [[ ! -f "$CRON_FILE" ]]; then
|
if [[ ! -f "$CRON_FILE" ]]; then
|
||||||
log "Installing freshclam cron job (twice daily)..."
|
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'
|
sudo tee "$CRON_FILE" > /dev/null <<'EOF'
|
||||||
# Update ClamAV virus definitions twice a day
|
# Update ClamAV virus definitions twice a day
|
||||||
0 */12 * * * root /usr/bin/freshclam --quiet 2>/dev/null
|
0 */12 * * * root /usr/bin/freshclam --quiet 2>/dev/null
|
||||||
EOF
|
EOF
|
||||||
|
# 644 = readable by all (cron needs to read it), writable only by root
|
||||||
sudo chmod 644 "$CRON_FILE"
|
sudo chmod 644 "$CRON_FILE"
|
||||||
else
|
else
|
||||||
skip "freshclam cron job already configured."
|
skip "freshclam cron job already configured."
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,35 @@
|
||||||
#!/bin/bash
|
#!/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
|
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"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing Ardour (Flatpak)..."
|
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
|
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
|
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"
|
apply_flatpak_theme "org.ardour.Ardour"
|
||||||
|
|
||||||
log "Ardour installed."
|
log "Ardour installed."
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,31 @@
|
||||||
#!/bin/bash
|
#!/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
|
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"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing Audacity (Flatpak)..."
|
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
|
ensure_flatpak
|
||||||
|
|
||||||
|
# Install from Flathub. -y suppresses the interactive confirmation.
|
||||||
flatpak install -y flathub org.audacityteam.Audacity
|
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"
|
apply_flatpak_theme "org.audacityteam.Audacity"
|
||||||
|
|
||||||
log "Audacity installed."
|
log "Audacity installed."
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,45 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -euo pipefail
|
||||||
|
# Load shared logging helpers and Flatpak utility functions
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing Blender (Flatpak)..."
|
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
|
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
|
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"
|
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)..."
|
log "Installing POV-Ray (pacman)..."
|
||||||
sudo pacman -S --noconfirm --needed povray
|
sudo pacman -S --noconfirm --needed povray
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,37 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -euo pipefail
|
||||||
|
# Load shared logging helpers from the dotfiles lib
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
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..."
|
log "Installing btrfs-progs..."
|
||||||
sudo pacman -S --noconfirm --needed 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)..."
|
log "Installing butter (AUR)..."
|
||||||
yay -S --answerdiff None --answerclean All --noconfirm butter
|
yay -S --answerdiff None --answerclean All --noconfirm butter
|
||||||
|
|
||||||
log "butter installed."
|
log "butter installed."
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,66 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -euo pipefail
|
||||||
|
# Load shared logging helpers from the dotfiles lib
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
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..."
|
log "Installing CalDAV sync stack..."
|
||||||
sudo pacman -S --noconfirm --needed vdirsyncer khal python-icalendar
|
sudo pacman -S --noconfirm --needed vdirsyncer khal python-icalendar
|
||||||
|
|
||||||
# ── Credentials ───────────────────────────────────────────────────────────────
|
# ── 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 "CalDAV server URL (e.g. https://dav.example.com/cal/): " CALDAV_URL
|
||||||
read -rp "Username : " CALDAV_USER
|
read -rp "Username : " CALDAV_USER
|
||||||
read -rsp "Password : " CALDAV_PASS; echo
|
read -rsp "Password : " CALDAV_PASS; echo
|
||||||
read -rp "Calendar display name [Personal] : " CAL_NAME
|
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}"
|
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"
|
CALDAV_DIR="$HOME/.local/share/calendars"
|
||||||
mkdir -p "$CALDAV_DIR"
|
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..."
|
log "Writing ~/.config/vdirsyncer/config..."
|
||||||
mkdir -p ~/.config/vdirsyncer ~/.local/share/vdirsyncer/status
|
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
|
cat > ~/.config/vdirsyncer/config << EOF
|
||||||
[general]
|
[general]
|
||||||
status_path = "$HOME/.local/share/vdirsyncer/status/"
|
status_path = "$HOME/.local/share/vdirsyncer/status/"
|
||||||
|
|
@ -39,15 +82,24 @@ url = "$CALDAV_URL"
|
||||||
username = "$CALDAV_USER"
|
username = "$CALDAV_USER"
|
||||||
password = "$CALDAV_PASS"
|
password = "$CALDAV_PASS"
|
||||||
EOF
|
EOF
|
||||||
|
# Restrict permissions: config contains plaintext password
|
||||||
chmod 600 ~/.config/vdirsyncer/config
|
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)..."
|
log "Discovering CalDAV collections (confirm any prompts with y)..."
|
||||||
yes | vdirsyncer discover calendars || true
|
yes | vdirsyncer discover calendars || true
|
||||||
|
|
||||||
|
# Pull all events from the server for the first time.
|
||||||
log "Running initial sync..."
|
log "Running initial sync..."
|
||||||
vdirsyncer 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)..."
|
log "Writing ~/.config/khal/config (per-calendar entries)..."
|
||||||
mkdir -p ~/.config/khal
|
mkdir -p ~/.config/khal
|
||||||
python3 - "$CALDAV_DIR" << 'PYEOF'
|
python3 - "$CALDAV_DIR" << 'PYEOF'
|
||||||
|
|
@ -55,13 +107,18 @@ import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
cal_root = Path(sys.argv[1])
|
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"]
|
colors = ["light blue", "light green", "light red", "light magenta", "light cyan"]
|
||||||
dirs = sorted(d for d in cal_root.iterdir() if d.is_dir())
|
dirs = sorted(d for d in cal_root.iterdir() if d.is_dir())
|
||||||
|
|
||||||
lines = ["[calendars]"]
|
lines = ["[calendars]"]
|
||||||
for i, d in enumerate(dirs):
|
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)]}"]
|
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 += [
|
lines += [
|
||||||
"", "[sqlite]", "path = ~/.local/share/khal/khal.db",
|
"", "[sqlite]", "path = ~/.local/share/khal/khal.db",
|
||||||
"", "[locale]", "timeformat = %H:%M", "dateformat = %Y-%m-%d",
|
"", "[locale]", "timeformat = %H:%M", "dateformat = %Y-%m-%d",
|
||||||
|
|
@ -72,6 +129,10 @@ lines += [
|
||||||
PYEOF
|
PYEOF
|
||||||
|
|
||||||
# ── ICS → calendar.vim cache converter ───────────────────────────────────────
|
# ── 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..."
|
log "Installing ics-to-calendarim converter..."
|
||||||
mkdir -p ~/.local/bin
|
mkdir -p ~/.local/bin
|
||||||
cat > ~/.local/bin/ics-to-calendarim << 'PYEOF'
|
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"
|
CACHE_DIR = Path.home() / ".cache/calendar.vim/local"
|
||||||
KHAL_CFG = Path.home() / ".config/khal/config"
|
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"]
|
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"]
|
KHAL_COLORS = ["light blue", "light green", "light red", "light magenta", "light cyan"]
|
||||||
|
|
||||||
def to_naive(dt):
|
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 isinstance(dt, datetime):
|
||||||
if dt.tzinfo is not None:
|
if dt.tzinfo is not None:
|
||||||
dt = dt.astimezone(timezone.utc).replace(tzinfo=None)
|
dt = dt.astimezone(timezone.utc).replace(tzinfo=None)
|
||||||
|
|
@ -109,10 +179,12 @@ def to_naive(dt):
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
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())):
|
for idx, cal_dir in enumerate(sorted(d for d in CALDAV_DIR.iterdir() if d.is_dir())):
|
||||||
cal_id = cal_dir.name
|
cal_id = cal_dir.name
|
||||||
|
# Human-readable name: replace underscores with spaces and title-case
|
||||||
cal_summary = cal_dir.name.replace("_", " ").title()
|
cal_summary = cal_dir.name.replace("_", " ").title()
|
||||||
cal_items.append({
|
cal_items.append({
|
||||||
"id": cal_id,
|
"id": cal_id,
|
||||||
|
|
@ -121,15 +193,17 @@ def main():
|
||||||
"foregroundColor": "#ffffff",
|
"foregroundColor": "#ffffff",
|
||||||
})
|
})
|
||||||
|
|
||||||
by_month: dict = {}
|
by_month: dict = {} # keyed by (year, month) → list of event objects
|
||||||
for ics in cal_dir.glob("*.ics"):
|
for ics in cal_dir.glob("*.ics"):
|
||||||
try:
|
try:
|
||||||
cal = iCal.from_ical(ics.read_bytes())
|
cal = iCal.from_ical(ics.read_bytes())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"warning: skipping {ics.name}: {e}", file=sys.stderr)
|
print(f"warning: skipping {ics.name}: {e}", file=sys.stderr)
|
||||||
continue
|
continue
|
||||||
|
# Walk all VEVENT components in the ICS file
|
||||||
for comp in cal.walk("VEVENT"):
|
for comp in cal.walk("VEVENT"):
|
||||||
dtstart = comp.get("DTSTART")
|
dtstart = comp.get("DTSTART")
|
||||||
|
# If DTEND is missing, fall back to DTSTART (zero-duration event)
|
||||||
dtend = comp.get("DTEND") or comp.get("DTSTART")
|
dtend = comp.get("DTEND") or comp.get("DTSTART")
|
||||||
if not dtstart:
|
if not dtstart:
|
||||||
continue
|
continue
|
||||||
|
|
@ -140,21 +214,28 @@ def main():
|
||||||
uid = str(comp.get("UID", ics.stem))
|
uid = str(comp.get("UID", ics.stem))
|
||||||
summary = str(comp.get("SUMMARY", ""))
|
summary = str(comp.get("SUMMARY", ""))
|
||||||
key = (yr, mo)
|
key = (yr, mo)
|
||||||
|
# Group events by month so we write one JSON file per month
|
||||||
by_month.setdefault(key, []).append({
|
by_month.setdefault(key, []).append({
|
||||||
"id": uid,
|
"id": uid,
|
||||||
"summary": summary,
|
"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},
|
"start": {"dateTime": s_str} if is_timed else {"date": s_str},
|
||||||
"end": {"dateTime": e_str} if is_timed else {"date": e_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():
|
for (yr, mo), events in by_month.items():
|
||||||
out = CACHE_DIR / cal_id / f"{yr:04d}" / f"{mo:02d}"
|
out = CACHE_DIR / cal_id / f"{yr:04d}" / f"{mo:02d}"
|
||||||
out.mkdir(parents=True, exist_ok=True)
|
out.mkdir(parents=True, exist_ok=True)
|
||||||
(out / "0").write_text(json.dumps({"items": events}))
|
(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}))
|
(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())
|
cal_dirs = sorted(d for d in CALDAV_DIR.iterdir() if d.is_dir())
|
||||||
khal_lines = ["[calendars]"]
|
khal_lines = ["[calendars]"]
|
||||||
for i, d in enumerate(cal_dirs):
|
for i, d in enumerate(cal_dirs):
|
||||||
|
|
@ -170,15 +251,25 @@ def main():
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
PYEOF
|
PYEOF
|
||||||
|
# Make the helper executable so it can be called directly from the PATH
|
||||||
chmod +x ~/.local/bin/ics-to-calendarim
|
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..."
|
log "Running initial calendar.vim cache build..."
|
||||||
~/.local/bin/ics-to-calendarim
|
~/.local/bin/ics-to-calendarim
|
||||||
|
|
||||||
# ── systemd user timer ────────────────────────────────────────────────────────
|
# ── 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)..."
|
log "Installing vdirsyncer systemd user timer (every 15 min)..."
|
||||||
mkdir -p ~/.config/systemd/user
|
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
|
cat > ~/.config/systemd/user/vdirsyncer.service << EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Sync CalDAV and rebuild calendar.vim cache
|
Description=Sync CalDAV and rebuild calendar.vim cache
|
||||||
|
|
@ -190,6 +281,9 @@ ExecStart=/usr/bin/vdirsyncer sync
|
||||||
ExecStartPost=$HOME/.local/bin/ics-to-calendarim
|
ExecStartPost=$HOME/.local/bin/ics-to-calendarim
|
||||||
EOF
|
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
|
cat > ~/.config/systemd/user/vdirsyncer.timer << EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Run vdirsyncer every 15 minutes
|
Description=Run vdirsyncer every 15 minutes
|
||||||
|
|
@ -203,7 +297,9 @@ Unit=vdirsyncer.service
|
||||||
WantedBy=timers.target
|
WantedBy=timers.target
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
# Reload systemd user daemon so it picks up the new unit files
|
||||||
systemctl --user daemon-reload
|
systemctl --user daemon-reload
|
||||||
|
# Enable and immediately start the timer; "--now" starts it without a reboot
|
||||||
systemctl --user enable --now vdirsyncer.timer
|
systemctl --user enable --now vdirsyncer.timer
|
||||||
|
|
||||||
log "CalDAV sync configured. Events will appear in calendar.vim automatically."
|
log "CalDAV sync configured. Events will appear in calendar.vim automatically."
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,29 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -euo pipefail
|
||||||
|
# Load shared logging helpers from the dotfiles lib
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
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)..."
|
log "Installing Cecilia (AUR)..."
|
||||||
yay -S --answerdiff None --answerclean All --noconfirm cecilia
|
yay -S --answerdiff None --answerclean All --noconfirm cecilia
|
||||||
|
|
||||||
log "Cecilia installed."
|
log "Cecilia installed."
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,33 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -euo pipefail
|
||||||
|
# Load shared logging helpers and Flatpak utility functions
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing Chromium (Flatpak)..."
|
log "Installing Chromium (Flatpak)..."
|
||||||
|
|
||||||
|
# ensure_flatpak: verifies Flatpak and Flathub are set up; idempotent.
|
||||||
ensure_flatpak
|
ensure_flatpak
|
||||||
|
|
||||||
|
# Install Chromium from Flathub. -y skips the confirmation prompt.
|
||||||
flatpak install -y flathub org.chromium.Chromium
|
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"
|
apply_flatpak_theme "org.chromium.Chromium"
|
||||||
|
|
||||||
log "Chromium installed."
|
log "Chromium installed."
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,42 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -euo pipefail
|
||||||
|
# Load shared logging helpers from the dotfiles lib
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing Claude Code via npm..."
|
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
|
if ! command -v npm &>/dev/null; then
|
||||||
log "Sourcing nvm to get npm..."
|
log "Sourcing nvm to get npm..."
|
||||||
export NVM_DIR="$HOME/.nvm"
|
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"
|
[[ -s "$NVM_DIR/nvm.sh" ]] && source "$NVM_DIR/nvm.sh"
|
||||||
fi
|
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
|
npm install -g @anthropic-ai/claude-code
|
||||||
|
|
||||||
log "Claude Code installed."
|
log "Claude Code installed."
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,60 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -euo pipefail
|
||||||
|
# Load shared logging helpers from the dotfiles lib
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
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)..."
|
log "Installing Cockpit (core + official plugins)..."
|
||||||
sudo pacman -S --noconfirm --needed \
|
sudo pacman -S --noconfirm --needed \
|
||||||
cockpit \
|
cockpit \
|
||||||
cockpit-pcp \
|
cockpit-pcp \
|
||||||
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)..."
|
log "Installing Cockpit AUR plugins (machines, podman, navigator)..."
|
||||||
yay -S --answerdiff None --answerclean All --noconfirm \
|
yay -S --answerdiff None --answerclean All --noconfirm \
|
||||||
cockpit-machines \
|
cockpit-machines \
|
||||||
cockpit-podman \
|
cockpit-podman \
|
||||||
cockpit-navigator
|
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..."
|
log "Enabling Cockpit socket..."
|
||||||
sudo systemctl enable cockpit.socket
|
sudo systemctl enable cockpit.socket
|
||||||
|
|
||||||
log "Cockpit enabled. Web UI available at https://localhost:9090"
|
log "Cockpit enabled. Web UI available at https://localhost:9090"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,28 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -euo pipefail
|
||||||
|
# Load shared logging helpers from the dotfiles lib
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
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..."
|
log "Installing Code::Blocks..."
|
||||||
sudo pacman -S --noconfirm --needed codeblocks
|
sudo pacman -S --noconfirm --needed codeblocks
|
||||||
|
|
||||||
log "Code::Blocks installed."
|
log "Code::Blocks installed."
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,28 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -euo pipefail
|
||||||
|
# Load shared logging helpers from the dotfiles lib
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
|
# ── croc (official repos) ─────────────────────────────────────────────────────
|
||||||
log "Installing croc (file transfer)..."
|
log "Installing croc (file transfer)..."
|
||||||
sudo pacman -S --noconfirm --needed croc
|
sudo pacman -S --noconfirm --needed croc
|
||||||
|
|
||||||
log "croc installed."
|
log "croc installed."
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,39 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -euo pipefail
|
||||||
|
# Load shared logging helpers from the dotfiles lib
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
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..."
|
log "Installing pgcli..."
|
||||||
sudo pacman -S --noconfirm --needed 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)..."
|
log "Installing mycli (AUR)..."
|
||||||
yay -S --answerdiff None --answerclean All --noconfirm mycli
|
yay -S --answerdiff None --answerclean All --noconfirm mycli
|
||||||
|
|
||||||
log "DB clients installed."
|
log "DB clients installed."
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,42 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -euo pipefail
|
||||||
|
# Load shared logging helpers from the dotfiles lib
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
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..."
|
log "Installing ddrescue..."
|
||||||
sudo pacman -S --noconfirm --needed 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)..."
|
log "Installing f3 (AUR)..."
|
||||||
yay -S --answerdiff None --answerclean All --noconfirm f3
|
yay -S --answerdiff None --answerclean All --noconfirm f3
|
||||||
|
|
||||||
log "Disk recovery tools installed."
|
log "Disk recovery tools installed."
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,46 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -euo pipefail
|
||||||
|
# Load shared logging helpers from the dotfiles lib
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
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..."
|
log "Installing Docker and Docker Compose..."
|
||||||
sudo pacman -S --noconfirm --needed docker 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..."
|
log "Enabling Docker service..."
|
||||||
sudo systemctl enable 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..."
|
log "Adding $USER to docker group..."
|
||||||
sudo usermod -aG docker "$USER"
|
sudo usermod -aG docker "$USER"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,34 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -euo pipefail
|
||||||
|
# Load shared logging helpers from the dotfiles lib
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
# chocolate-doom: faithful vanilla Doom port; freedoom: free game data (playable without IWADs)
|
# chocolate-doom: faithful vanilla Doom port; freedoom: free game data (playable without IWADs)
|
||||||
log "Installing Chocolate Doom and Freedoom data..."
|
log "Installing Chocolate Doom and Freedoom data..."
|
||||||
sudo pacman -S --noconfirm --needed chocolate-doom freedoom
|
sudo pacman -S --noconfirm --needed chocolate-doom freedoom
|
||||||
|
|
||||||
log "Doom installed."
|
log "Doom installed."
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,42 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -euo pipefail
|
||||||
|
# Load shared logging helpers from the dotfiles lib
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing FFmpeg extras (thumbnailer + GStreamer codecs)..."
|
log "Installing FFmpeg extras (thumbnailer + GStreamer codecs)..."
|
||||||
sudo pacman -S --noconfirm --needed \
|
sudo pacman -S --noconfirm --needed \
|
||||||
ffmpeg ffmpegthumbnailer \
|
ffmpeg ffmpegthumbnailer \
|
||||||
gst-libav gst-plugins-good gst-plugins-bad gst-plugins-ugly
|
gst-libav gst-plugins-good gst-plugins-bad gst-plugins-ugly
|
||||||
|
|
||||||
log "FFmpeg extras installed."
|
log "FFmpeg extras installed."
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,35 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -euo pipefail
|
||||||
|
# Load shared logging helpers and Flatpak utility functions
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing Firefox (Flatpak)..."
|
log "Installing Firefox (Flatpak)..."
|
||||||
|
|
||||||
|
# ensure_flatpak: checks that Flatpak and the Flathub remote are configured;
|
||||||
|
# idempotent — safe to call multiple times.
|
||||||
ensure_flatpak
|
ensure_flatpak
|
||||||
|
|
||||||
|
# Install from Flathub. -y skips interactive confirmation.
|
||||||
flatpak install -y flathub org.mozilla.firefox
|
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"
|
apply_flatpak_theme "org.mozilla.firefox"
|
||||||
|
|
||||||
log "Firefox installed."
|
log "Firefox installed."
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,69 @@
|
||||||
#!/bin/bash
|
#!/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
|
set -euo pipefail
|
||||||
|
# Load shared logging helpers from the dotfiles lib
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
log "Starting FreeIPA client installer..."
|
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)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
FREEIPA_DIR="$SCRIPT_DIR/../../FreeipaAnsible"
|
FREEIPA_DIR="$SCRIPT_DIR/../../FreeipaAnsible"
|
||||||
|
# Optional server-generated enrolment wrapper script
|
||||||
CLIENT_SCRIPT="$FREEIPA_DIR/freeipa-client.sh"
|
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"
|
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_DOMAIN="freeipa.abdelbaki.eu"
|
||||||
DEF_REALM="FREEIPA.ABDELBAKI.EU"
|
DEF_REALM="FREEIPA.ABDELBAKI.EU"
|
||||||
DEF_SERVER="freeipa.abdelbaki.eu"
|
DEF_SERVER="freeipa.abdelbaki.eu"
|
||||||
|
|
||||||
# ── Packages ──────────────────────────────────────────────────────────────────
|
# ── Packages ──────────────────────────────────────────────────────────────────
|
||||||
echo "[+] Installing FreeIPA client 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
|
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
|
if command -v yay &>/dev/null; then
|
||||||
echo "[+] Installing freeipa-client (AUR)..."
|
echo "[+] Installing freeipa-client (AUR)..."
|
||||||
yay -S --noconfirm --needed freeipa-client
|
yay -S --noconfirm --needed freeipa-client
|
||||||
|
|
@ -26,11 +72,18 @@ else
|
||||||
echo " Install yay then run: yay -S --needed freeipa-client"
|
echo " Install yay then run: yay -S --needed freeipa-client"
|
||||||
fi
|
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
|
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
|
command -v dialog &>/dev/null || pacman -S --noconfirm --needed dialog
|
||||||
|
|
||||||
# ── Dialog theme ──────────────────────────────────────────────────────────────
|
# ── 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
|
if [[ -z "${DIALOGRC:-}" ]] || [[ ! -f "${DIALOGRC:-/dev/null}" ]]; then
|
||||||
_TMP_D=$(mktemp -d)
|
_TMP_D=$(mktemp -d)
|
||||||
trap 'rm -rf "$_TMP_D"' EXIT
|
trap 'rm -rf "$_TMP_D"' EXIT
|
||||||
|
|
@ -68,6 +121,9 @@ fi
|
||||||
BT="FreeIPA Client Setup"
|
BT="FreeIPA Client Setup"
|
||||||
T=$(mktemp); trap 'rm -f "$T"' EXIT
|
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; }
|
d() { dialog --backtitle "$BT" "$@" 3>&1 1>&2 2>&3; }
|
||||||
input(){ d --title " $1 " --inputbox "$2" 10 64 "$3"; }
|
input(){ d --title " $1 " --inputbox "$2" 10 64 "$3"; }
|
||||||
pass() { d --title " $1 " --passwordbox "$2" 10 64; }
|
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; }
|
msg() { d --title " FreeIPA Client " --msgbox "$1" 10 64; }
|
||||||
|
|
||||||
# ── Enrollment choice ─────────────────────────────────────────────────────────
|
# ── 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 " \
|
CHOICE=$(d --title " FreeIPA Client Enrollment " \
|
||||||
--menu "\n Packages installed.\n How would you like to enroll this host?\n" \
|
--menu "\n Packages installed.\n How would you like to enroll this host?\n" \
|
||||||
13 64 3 \
|
13 64 3 \
|
||||||
|
|
@ -82,10 +140,14 @@ CHOICE=$(d --title " FreeIPA Client Enrollment " \
|
||||||
"manual" "Enter enrollment data manually" \
|
"manual" "Enter enrollment data manually" \
|
||||||
"skip" "Skip — enroll later") || CHOICE="skip"
|
"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() {
|
run_enroll() {
|
||||||
local args=("$@")
|
local args=("$@")
|
||||||
if [[ -x "$CLIENT_SCRIPT" ]]; then
|
if [[ -x "$CLIENT_SCRIPT" ]]; then
|
||||||
|
# Preferred path: use the pre-generated client script from the server
|
||||||
exec "$CLIENT_SCRIPT" "${args[@]}"
|
exec "$CLIENT_SCRIPT" "${args[@]}"
|
||||||
else
|
else
|
||||||
# Fall back to ipa-client-install directly
|
# Fall back to ipa-client-install directly
|
||||||
|
|
@ -94,6 +156,7 @@ run_enroll() {
|
||||||
local mkhomedir=true sudo_=true dns=true fido2=false
|
local mkhomedir=true sudo_=true dns=true fido2=false
|
||||||
declare -a fido2_users=()
|
declare -a fido2_users=()
|
||||||
|
|
||||||
|
# Parse our internal argument format and translate to ipa-client-install flags
|
||||||
for ((i=0; i<${#args[@]}; i++)); do
|
for ((i=0; i<${#args[@]}; i++)); do
|
||||||
case "${args[$i]}" in
|
case "${args[$i]}" in
|
||||||
--domain) dom="${args[$((i+1))]}"; ((i++)) ;;
|
--domain) dom="${args[$((i+1))]}"; ((i++)) ;;
|
||||||
|
|
@ -111,6 +174,7 @@ run_enroll() {
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Build the ipa-client-install command from parsed values
|
||||||
[[ -n "$dom" ]] && cmd+=(--domain "$dom")
|
[[ -n "$dom" ]] && cmd+=(--domain "$dom")
|
||||||
[[ -n "$rlm" ]] && cmd+=(--realm "$rlm")
|
[[ -n "$rlm" ]] && cmd+=(--realm "$rlm")
|
||||||
[[ -n "$srv" ]] && cmd+=(--server "$srv")
|
[[ -n "$srv" ]] && cmd+=(--server "$srv")
|
||||||
|
|
@ -125,14 +189,18 @@ run_enroll() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Answerfile mode ───────────────────────────────────────────────────────────
|
# ── 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
|
if [[ "$CHOICE" == "answerfile" ]]; then
|
||||||
AF_PATH=$(input "Answerfile path" \
|
AF_PATH=$(input "Answerfile path" \
|
||||||
"Path to the JSON answerfile:" \
|
"Path to the JSON answerfile:" \
|
||||||
"$DEFAULT_AF") || { msg " Enrollment cancelled."; exit 0; }
|
"$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"
|
[[ -z "$AF_PATH" ]] && AF_PATH="$DEFAULT_AF"
|
||||||
|
|
||||||
if [[ ! -f "$AF_PATH" ]]; then
|
if [[ ! -f "$AF_PATH" ]]; then
|
||||||
|
# Offer to create a template answerfile so the user can fill it in
|
||||||
if yn "Create answerfile" \
|
if yn "Create answerfile" \
|
||||||
" '$AF_PATH' does not exist.\n Create it with default values?"; then
|
" '$AF_PATH' does not exist.\n Create it with default values?"; then
|
||||||
mkdir -p "$(dirname "$AF_PATH")"
|
mkdir -p "$(dirname "$AF_PATH")"
|
||||||
|
|
@ -160,16 +228,19 @@ AFEOF
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Clear the dialog UI before running ipa-client-install (which prints to stdout)
|
||||||
clear
|
clear
|
||||||
run_enroll --answerfile "$AF_PATH"
|
run_enroll --answerfile "$AF_PATH"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Manual mode ───────────────────────────────────────────────────────────────
|
# ── Manual mode ───────────────────────────────────────────────────────────────
|
||||||
|
# Collect all enrolment parameters interactively via dialog prompts.
|
||||||
if [[ "$CHOICE" == "manual" ]]; then
|
if [[ "$CHOICE" == "manual" ]]; then
|
||||||
DOMAIN=$(input "IPA Domain" "FreeIPA domain:" "$DEF_DOMAIN") \
|
DOMAIN=$(input "IPA Domain" "FreeIPA domain:" "$DEF_DOMAIN") \
|
||||||
|| { msg " Enrollment cancelled."; exit 0; }
|
|| { msg " Enrollment cancelled."; exit 0; }
|
||||||
DOMAIN="${DOMAIN:-$DEF_DOMAIN}"
|
DOMAIN="${DOMAIN:-$DEF_DOMAIN}"
|
||||||
|
|
||||||
|
# Kerberos realm is conventionally the domain uppercased; pre-fill that guess
|
||||||
DEF_REALM_CALC="${DOMAIN^^}"
|
DEF_REALM_CALC="${DOMAIN^^}"
|
||||||
REALM=$(input "Kerberos Realm" "Kerberos realm (usually domain uppercased):" \
|
REALM=$(input "Kerberos Realm" "Kerberos realm (usually domain uppercased):" \
|
||||||
"$DEF_REALM_CALC") || REALM="$DEF_REALM_CALC"
|
"$DEF_REALM_CALC") || REALM="$DEF_REALM_CALC"
|
||||||
|
|
@ -179,6 +250,7 @@ if [[ "$CHOICE" == "manual" ]]; then
|
||||||
|| { msg " Enrollment cancelled."; exit 0; }
|
|| { msg " Enrollment cancelled."; exit 0; }
|
||||||
SERVER="${SERVER:-$DOMAIN}"
|
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)
|
DEF_HOST=$(hostname -f 2>/dev/null || hostname)
|
||||||
HOST=$(input "This Host FQDN" "Hostname to register (leave blank = current):" \
|
HOST=$(input "This Host FQDN" "Hostname to register (leave blank = current):" \
|
||||||
"$DEF_HOST") || HOST="$DEF_HOST"
|
"$DEF_HOST") || HOST="$DEF_HOST"
|
||||||
|
|
@ -186,6 +258,7 @@ if [[ "$CHOICE" == "manual" ]]; then
|
||||||
PRINCIPAL=$(input "Admin Principal" "IPA admin principal:" "admin") || PRINCIPAL="admin"
|
PRINCIPAL=$(input "Admin Principal" "IPA admin principal:" "admin") || PRINCIPAL="admin"
|
||||||
PRINCIPAL="${PRINCIPAL:-admin}"
|
PRINCIPAL="${PRINCIPAL:-admin}"
|
||||||
|
|
||||||
|
# Use passwordbox so the password is not echoed to the terminal
|
||||||
PASSWORD=$(pass "Admin Password" "Password for $PRINCIPAL@$REALM:") \
|
PASSWORD=$(pass "Admin Password" "Password for $PRINCIPAL@$REALM:") \
|
||||||
|| { msg " Enrollment cancelled."; exit 0; }
|
|| { msg " Enrollment cancelled."; exit 0; }
|
||||||
[[ -z "$PASSWORD" ]] && { msg " Password is required."; exit 1; }
|
[[ -z "$PASSWORD" ]] && { msg " Password is required."; exit 1; }
|
||||||
|
|
@ -202,6 +275,7 @@ if [[ "$CHOICE" == "manual" ]]; then
|
||||||
)
|
)
|
||||||
[[ -n "$NTP" ]] && ARGS+=(--ntp-server "$NTP")
|
[[ -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?" \
|
yn "Home Directories" " Auto-create home directories on first login?" \
|
||||||
&& true || ARGS+=(--no-mkhomedir)
|
&& true || ARGS+=(--no-mkhomedir)
|
||||||
|
|
||||||
|
|
@ -211,12 +285,14 @@ if [[ "$CHOICE" == "manual" ]]; then
|
||||||
yn "DNS Update" " Register this host's IP in IPA DNS?" \
|
yn "DNS Update" " Register this host's IP in IPA DNS?" \
|
||||||
&& true || ARGS+=(--no-dns-update)
|
&& true || ARGS+=(--no-dns-update)
|
||||||
|
|
||||||
|
# FIDO2/WebAuthn hardware token support (requires libfido2 + pamu2fcfg)
|
||||||
if yn "FIDO2" " Enable FIDO2/WebAuthn authentication?"; then
|
if yn "FIDO2" " Enable FIDO2/WebAuthn authentication?"; then
|
||||||
ARGS+=(--fido2)
|
ARGS+=(--fido2)
|
||||||
FIDO2_USERS=$(input "FIDO2 Users" \
|
FIDO2_USERS=$(input "FIDO2 Users" \
|
||||||
"Usernames to enable FIDO2 for (comma-separated, blank = all):" "") \
|
"Usernames to enable FIDO2 for (comma-separated, blank = all):" "") \
|
||||||
|| FIDO2_USERS=""
|
|| FIDO2_USERS=""
|
||||||
if [[ -n "$FIDO2_USERS" ]]; then
|
if [[ -n "$FIDO2_USERS" ]]; then
|
||||||
|
# Split comma-separated list and strip whitespace from each username
|
||||||
IFS=',' read -ra _U <<< "$FIDO2_USERS"
|
IFS=',' read -ra _U <<< "$FIDO2_USERS"
|
||||||
for u in "${_U[@]}"; do
|
for u in "${_U[@]}"; do
|
||||||
u="${u// /}"
|
u="${u// /}"
|
||||||
|
|
@ -230,6 +306,7 @@ if [[ "$CHOICE" == "manual" ]]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Skip ──────────────────────────────────────────────────────────────────────
|
# ── Skip ──────────────────────────────────────────────────────────────────────
|
||||||
|
# Packages are installed; print instructions for later manual enrolment.
|
||||||
echo ""
|
echo ""
|
||||||
echo "[✓] FreeIPA client packages installed."
|
echo "[✓] FreeIPA client packages installed."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
# Load shared log/skip/warn/err helpers from the installer library.
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing Geany (Flatpak)..."
|
log "Installing Geany and plugins..."
|
||||||
ensure_flatpak
|
# geany: lightweight GTK text editor / basic IDE with syntax highlighting.
|
||||||
flatpak install -y flathub org.geany.Geany
|
# geany-plugins: meta-package of community plugins (project manager, Git integration,
|
||||||
apply_flatpak_theme "org.geany.Geany"
|
# spell check, etc.) that ship separately from the core editor.
|
||||||
|
sudo pacman -S --noconfirm --needed geany geany-plugins
|
||||||
log "Geany installed."
|
log "Geany installed."
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
# Load shared log/skip/warn/err helpers from the installer library.
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing GIMP (Flatpak)..."
|
log "Installing GIMP..."
|
||||||
ensure_flatpak
|
# Install GIMP (GNU Image Manipulation Program) from the official Arch repos.
|
||||||
flatpak install -y flathub org.gimp.GIMP
|
# --noconfirm skips interactive prompts; --needed avoids reinstalling if current.
|
||||||
apply_flatpak_theme "org.gimp.GIMP"
|
sudo pacman -S --noconfirm --needed gimp
|
||||||
log "GIMP installed."
|
log "GIMP installed."
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
# Load shared log/skip/warn/err helpers from the installer library.
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing Gnuplot..."
|
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
|
sudo pacman -S --noconfirm --needed gnuplot
|
||||||
log "Gnuplot installed."
|
log "Gnuplot installed."
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
# Load shared log/skip/warn/err helpers from the installer library.
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing Himalaya (AUR)..."
|
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
|
yay -S --answerdiff None --answerclean All --noconfirm himalaya-bin
|
||||||
log "Himalaya installed."
|
log "Himalaya installed."
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
# Load shared log/skip/warn/err helpers from the installer library.
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing ImageMagick..."
|
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
|
sudo pacman -S --noconfirm --needed imagemagick
|
||||||
log "ImageMagick installed."
|
log "ImageMagick installed."
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
# Load shared log/skip/warn/err helpers from the installer library.
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing Inkscape (Flatpak)..."
|
log "Installing Inkscape..."
|
||||||
ensure_flatpak
|
# Inkscape is a vector graphics editor (SVG-native); available in the official repos.
|
||||||
flatpak install -y flathub org.inkscape.Inkscape
|
# --noconfirm skips interactive prompts; --needed avoids reinstalling if current.
|
||||||
apply_flatpak_theme "org.inkscape.Inkscape"
|
sudo pacman -S --noconfirm --needed inkscape
|
||||||
log "Inkscape installed."
|
log "Inkscape installed."
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
# Load shared log/skip/warn/err helpers from the installer library.
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing kubectl (pacman)..."
|
log "Installing Kubernetes tools (kubectl, podman-desktop)..."
|
||||||
sudo pacman -S --noconfirm --needed kubectl
|
# kubectl: official Kubernetes CLI for interacting with clusters.
|
||||||
|
# podman-desktop: native GUI for managing Podman containers and Kubernetes contexts.
|
||||||
log "Installing Podman Desktop (Flatpak)..."
|
# Both are available in the official Arch repos; --needed keeps the run idempotent.
|
||||||
ensure_flatpak
|
sudo pacman -S --noconfirm --needed kubectl podman-desktop
|
||||||
flatpak install -y flathub io.podman_desktop.PodmanDesktop
|
|
||||||
apply_flatpak_theme "io.podman_desktop.PodmanDesktop"
|
|
||||||
log "Kubernetes tools installed."
|
log "Kubernetes tools installed."
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
# Load shared log/skip/warn/err helpers from the installer library.
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing Kate (Flatpak)..."
|
log "Installing Kate..."
|
||||||
ensure_flatpak
|
# Kate is the KDE Advanced Text Editor; available in the official Arch repos.
|
||||||
flatpak install -y flathub org.kde.kate
|
# --noconfirm skips interactive prompts; --needed avoids reinstalling if current.
|
||||||
apply_flatpak_theme "org.kde.kate"
|
sudo pacman -S --noconfirm --needed kate
|
||||||
log "Kate installed."
|
log "Kate installed."
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
# Load shared log/skip/warn/err helpers from the installer library.
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing Kdenlive (Flatpak)..."
|
log "Installing Kdenlive..."
|
||||||
ensure_flatpak
|
# Kdenlive is the KDE non-linear video editor; available in the official Arch repos.
|
||||||
flatpak install -y flathub org.kde.kdenlive
|
# --noconfirm skips interactive prompts; --needed avoids reinstalling if current.
|
||||||
apply_flatpak_theme "org.kde.kdenlive"
|
sudo pacman -S --noconfirm --needed kdenlive
|
||||||
log "Kdenlive installed."
|
log "Kdenlive installed."
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
# Load shared log/skip/warn/err helpers from the installer library.
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
||||||
|
|
||||||
log "Installing Krita (Flatpak)..."
|
log "Installing Krita..."
|
||||||
ensure_flatpak
|
# Krita is a professional digital painting application; available in the official repos.
|
||||||
flatpak install -y flathub org.kde.krita
|
# --noconfirm skips interactive prompts; --needed avoids reinstalling if current.
|
||||||
apply_flatpak_theme "org.kde.krita"
|
sudo pacman -S --noconfirm --needed krita
|
||||||
log "Krita installed."
|
log "Krita installed."
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue