My Dotfiles

PipelineLatest release

A Hyprland desktop for Arch Linux and CachyOS, with Zsh, Neovim, Alacritty, tmux, Waybar, and Mako, set up by a single Ansible playbook.

Hyprland demo: a Hyprlauncher search, a Mako notification, and the Hyprlock screen over Neovim, Fastfetch, and Waybar

Searching with Hyprlauncher, a Mako notification, then Hyprlock, recorded by make screenshot from the Hyprland config in this repository. Click it for the full-size still.

Changelog · Releases · Arch installation guide · Ansible guide

Highlights

  • Hyprland 0.55+ configured in Lua, with Waybar, Hyprlauncher, Hyprlock, and per-monitor wallpapers.
  • Mako notifications in the top-right corner, silenced while steam-optimize runs a game, except for critical alerts.
  • Zsh with Oh My Zsh, Neovim 0.7.2+ with native LSP, Vim, Alacritty, and tmux.
  • A Gruvbox Dark palette shared by Hyprland, Waybar, Mako, Hyprlock, tmux, Neovim, and Vim.
  • One Ansible playbook for packages, system settings, and dotfiles on Arch Linux/CachyOS, Debian/Ubuntu, and Fedora.
  • Gaming and GPU tuning: ananicy-cpp, power-profiles-daemon, AMD CPU settings, and LACT profiles for AMD and NVIDIA.

Quick start

You need Git, make, a regular user with sudo access, and Ansible with the collections in ansible/requirements.yml. The full ansible package ships them; with ansible-core alone, install them into the repository after cloning.

git clone https://gitlab.com/pad92/dotfiles.git ~/.dotfiles
cd ~/.dotfiles
# Only with ansible-core:
make galaxy
make check   # preview
make deploy

make deploy installs packages and deploys the configuration to your home directory. Installation is additive: packages that were already installed stay unmanaged.

Ansible reaches the machine over SSH, even when it is the one you type on. HOST selects a host of ansible/inventory.yml and defaults to the short hostname (uname -n up to the first dot). The target needs an SSH server; the repository only has to be cloned on the machine you run make from, and that clone can be deleted once deployed: make deploy HOST=PadsP5560 configures the laptop from the desktop. A new machine first gets an inventory entry; see Per-host settings.

Common tasks

make help lists the targets and their variables. deploy, check, plan, tags and tasks share these variables, except that plan refuses TAGS and SKIP_TAGS: a selection could skip the stop after the package plan and let the next roles change the host.

VariableEffect
PLAYBOOKsite (default, the workstation) or obs (OBS only).
HOSTInventory host or pattern; defaults to this machine.
TAGSRun only these comma-separated tags (--tags).
SKIP_TAGSSkip these comma-separated tags (--skip-tags).
BECOME_PASSAny value asks for the sudo password (--ask-become-pass).
ANSIBLE_ARGSAny other option, passed last to ansible-playbook.
GoalCommand
Preview a full runmake check
Print only the package planmake plan
List the tags, or the tasks they runmake tags / make tasks TAGS=dotfiles
Deploy the dotfiles onlymake deploy TAGS=dotfiles_deploy
Preview the dotfiles onlymake check TAGS=dotfiles
Remove packages this installer addedmake deploy ANSIBLE_ARGS="-e packages_action=reconcile"
Set up the Steam groupmake deploy TAGS=packages,gaming BECOME_PASS=1
Install only OBS and its demo scenesmake deploy PLAYBOOK=obs BECOME_PASS=1
Upgrade the whole system on Archmake deploy ANSIBLE_ARGS="-e packages_upgrade_system=true"
Install the pinned collectionsmake galaxy, or make galaxy-force to reinstall them
Remove backups and leftoversmake clean, or make clean DRY_RUN=1 to list them only

make install and make install-obs remain as aliases of make deploy and make deploy PLAYBOOK=obs.

Reconciliation previews the removals and asks for confirmation.

make clean removes what deployments and earlier versions left behind. In the home directory (CLEAN_ROOT, $HOME by default) it deletes the backups the dotfiles role made next to the paths it manages (<path>.bak and <path>.<pid>.<date>@<time>~). It also deletes the broken links under the paths the role deploys, such as a link into a deleted ~/.dotfiles clone or a .wants link to a unit that is gone, and leaves other broken links in the home alone. It deletes the files a role no longer ships, such as the audio role's former 10-realtime.conf, while they still hold the shipped content; an edited copy or a link is kept and reported. In the repository it removes the Git metadata of the retired submodules (Vundle, tpm, dotbot, the Zsh plugins: their [submodule] sections and their clones under .git/modules), the records of worktrees whose directory is gone, the Python tool caches and untracked broken links. It does not look into nested Git checkouts or Python virtual environments. A submodule clone with a commit that no remote branch or tag holds, or whose checkout still exists, is kept and reported. Everything is listed before anything changes. The backups, the submodule metadata and the worktree records go only after you type REMOVE on a terminal; without a terminal they stay. Caches, broken links and unchanged obsolete files go without asking. DRY_RUN accepts only 1 (list) and 0. make ansible-backup-ls and make ansible-backup-rm remain as aliases of make clean DRY_RUN=1 and make clean, and ANSIBLE_BACKUP_ROOT still selects the home.

Package selection and Arch specifics Package groups come from OS defaults and detected GPUs. Each machine sets its own list, `packages_host_groups`, in `ansible/host_vars/.yml`, or you pass `-e packages_group_selection=...` for one run. Automatic GPU selection requires every controller to be identified. Package variables load by OS family, then use the first matching exact-version, major-version, or distribution override. On Arch, Ansible configures pacman, enables the standard `multilib` repository, and runs `pacman -Syu` when it enables that repository. Missing AUR packages use `yay`, which Ansible can build as the regular user. Set `packages_aur_action=ask` to confirm AUR work or `skip` to leave it out. Native makepkg builds disable debug packages and may not run on a different CPU.

Ansible tags

Run a subset with make deploy TAGS=<tag>. The supported-distribution check and the read-only package group selection always run. The Ansible guide lists the roles and covers variables, package ownership, and system settings.

TagsAction
package_managerConfigure pacman and makepkg.
packagesConfigure pacman, then install and reconcile packages.
packages_install, packages_reconcileInstall packages; the latter also runs reconciliation.
pacman, pacman_configConfigure /etc/pacman.conf.
yayConfigure pacman and run the AUR flow only.
systemRun every host role from lact to gnome_keyring, and makepkg.
package_servicesEnable the services and user groups of the selected package groups, and Docker.
dockerAdd the Docker group users.
hardeningConfigure SSH client defaults and desktop sysctls.
ssh_client_hardening, kernel_hardeningConfigure SSH client defaults or desktop sysctls.
dnsMake systemd-resolved the only resolver, for the host and Docker.
vpnConfigure the openfortivpn SAML VPN.
gpu, gpu_modulesConfigure NVIDIA/i915 kernel modules and hybrid runtime PM.
makepkg, makepkg_configConfigure native Arch builds.
cpu_power, amd_cpu_performanceApply supported AMD CPU settings.
time_syncEnable systemd-timesyncd when no other NTP client runs.
power_saveSet Wi-Fi and audio power-saving module options on a portable machine.
wireless_regdomSet the Wi-Fi regulatory domain (wireless_regdom_country).
gnome_keyringUnlock the GNOME keyring at a TTY login through PAM.
gamingCheck dependencies and configure services for the Steam group.
lactManage /etc/lact/config.yaml.
obs, obs_config, obs_virtualcamConfigure OBS for 22_obs: record and stream profiles, palette scenes.
3dprintInstall PrusaSlicer and F3D, and set up F3D previews of 3D files in Nautilus.
dotfilesRun the dotfiles, audio and agent_cli roles.
audioWrite the PipeWire and WirePlumber configuration: clean mic and voice effects.
dotfiles_deployDeploy the configuration; clone the pinned Vim and Zsh plugins.
agent_cliSet the Claude Code and Codex status lines.
dotfiles_claudeAdd the Claude Code status line when claude is installed.
dotfiles_codexSet the Codex status line when codex is installed.
systemdDeploy user units and enable the units in dotfiles_systemd_graphical_units.
hyprlock_carouselRender the lock-screen carousel helper.

Changed files notify their handlers: Mako and Waybar reload, active audio services reload or restart (the SwayOSD server restarts with them), and active user units whose definitions changed restart after daemon-reload. Hypridle needs WAYLAND_DISPLAY in the user systemd environment to start.

Desktop

The Hyprland configuration is written in Lua. See the session guide for TTY startup, GNOME Keyring PAM, UWSM, and the systemd session.

ShortcutAction
SUPER + ReturnTerminal
SUPER + E / C / WFile manager / editor / browser
SUPER + DApplication menu
SUPER + F1List the keybindings Hyprland has loaded
SUPER + SHIFT + ReturnPassword manager (Proton Pass)
SUPER + SHIFT + QClose the window
SUPER + F / SUPER + ALT + SpaceFullscreen / floating
ALT + TabCycle windows and raise the focused one
SUPER + ArrowsChange focus; add Shift to move the window
SUPER + [ / SUPER + ]Focus the window on the left / right
SUPER + Left click / Right clickDrag to move / resize the window
SUPER + [0-9]Switch workspace; add Shift to move the window there
SUPER + ALT + [0-9]Send the window to a workspace and stay (keypad too)
CTRL + SUPER + Left / RightPrevious / next workspace
SUPER + L / SUPER + DeleteLock / logout menu
SUPER + SHIFT + VClipboard history
SUPER + ALT + RightChange wallpaper
SUPER + NDismiss notifications; Shift restores the last
Print / SUPER + PScreenshot of a region, then edit it in Swappy
CTRL + Print / SUPER + CTRL + PRecord a window (click) or a region (drag); press again to stop
CTRL + SHIFT + Print / SUPER + CTRL + SHIFT + PRecord the focused screen; press again to stop
SUPER + Page Up / Page DownZoom in / out around the pointer; SUPER + Home resets

Volume, media, brightness and screenshot keys also work on the lock screen. Caps Lock shows its state in an on-screen indicator.

Adding CTRL to a screenshot key records a video instead, and SHIFT widens the recording to the whole screen.

Mako shows up to five notifications in the top-right corner, newest first, and draws them above fullscreen windows. steam-optimize turns on do-not-disturb during games; critical alerts still appear and stay until dismissed.

awww.sh picks wallpapers per monitor from local files or NFS/CIFS mounts. Copy its sample to ~/.config/awww/awww.conf to configure sources. Renders are cached with a 2 GiB default quota, and awww_random.timer changes wallpapers every 30 minutes. ImageMagick is required.

Recordings are H.264 MP4 files at 30 fps, without sound, saved in ~/Videos/Recordings (the XDG videos folder). A few seconds of a 1440p screen take a few hundred kilobytes. Frames are copied continuously, so a still screen records correctly, and encoding a 1440p screen uses about 1.5 CPU cores while it runs. The path is copied to the clipboard when the recording stops. While it runs, Waybar shows a red REC label; clicking it stops the recording. A window is recorded as the area it covered when you picked it: if you move it, it leaves the frame, and anything drawn over it is recorded too.

Settings are environment variables of the Hyprland session (for example in ~/.config/uwsm/env-hyprland), documented at the top of hypr-record.sh: HYPR_RECORD_AUDIO=1 adds the default audio input, and HYPR_RECORD_HWENC=1 with HYPR_RECORD_VAAPI_DEVICE encodes on the GPU, falling back to libx264 if the encoder does not start.

Hyprlock shows the time, battery, and current media. Its carousel option uses the AWWW sources; see the Hyprlock template.

Gaming and GPUs

Select the 21_steam group, then run make deploy TAGS=packages,gaming BECOME_PASS=1. The group installs ananicy-cpp and power-profiles-daemon instead of GameMode. Ansible enables both services and stops if another known CPU power manager is active or enabled. On CachyOS it also adds the CachyOS Ananicy rules, Proton, and cachyos-settings, which provides game-performance. steam-optimize works without game-performance and sets shader-cache limits for AMD and NVIDIA.

The amd_cpu_performance tag applies balance_performance EPP through the powersave governor, only when AMD P-State is active and no known power manager is running. X3D CCD mode is opt-in. A failed EPP update restores the original governor and preference.

The lact tag manages /etc/lact/config.yaml with the GPU identifiers reported by the running LACT daemon. It keeps existing profiles and fan curves, backs up changes, and restarts lactd when needed. It adds Auto, Adaptive, and PreferMaximumPerformance profiles, named after LACT's PowerMizer modes, for detected AMD and NVIDIA GPUs. Intel GPU settings are left unchanged.

See AMD options and gaming setup for overrides and dependencies.

Customize

Set your Git identity in the untracked ~/.gitconfig.local; the managed .gitconfig includes it.

ConfigurationLocation
Shell and loginZsh files and .zshrc
Hyprland.config/hypr/
Waybar.config/waybar/
Notifications.config/mako/config
Terminal and tmuxAlacritty and .tmux.conf
EditorsNeovim and Vim
Wallpaper settingsAWWW configuration sample

Static files are copied from ansible/roles/dotfiles/files/ and host-aware files are rendered from ansible/roles/dotfiles/templates/. Managed paths that conflict are backed up before replacement. Unit symlinks stay links; deployment stops if a regular file or directory occupies one of their destinations.

Shell and editors

Zsh uses Oh My Zsh, autosuggestions, syntax highlighting, and the aliases and functions in zsh/, including arch_update, clean_arch, extract, mtail, and GPG helpers. mtail follows log files across rotation; quote globs to include files created later:

mtail '*.log' '/var/log/app/*.log'

Completion comes from zsh, the packages and the plugins, in that order. Commands that only ship a bash completion are completed through bashcompinit when their file works without the bash-completion helpers; otherwise zsh completes file names. The caches live in ~/.zcache: delete ~/.zcache/bash-completions or ~/.zcache/generated/ to rebuild them.

Neovim uses Lua, Telescope, Treesitter, Mason, and native LSP. Neovim 0.8+ loads plugins with lazy.nvim; Neovim 0.7.2 uses a compatibility loader with older plugin releases. Vim loads its plugins as a Vim package from ~/.vim/pack/dotfiles/start/, cloned at the commits the role pins.

Zed is installed on Arch with the 10_sre_devops package group. In Zed, CTRL + ALT + V saves the active file and runs vault-toggle on it: a plain file is encrypted with Ansible Vault, an encrypted one is decrypted in place. The playbook manages ~/.config/zed/tasks.json and keymap.json for this, so edits made to them in Zed are replaced (with a .bak copy) on the next run. settings.json is left alone.

vault-toggle takes the password from ANSIBLE_VAULT_PASSWORD_FILE or ANSIBLE_VAULT_IDENTITY_LIST, then from the project ansible.cfg, then from the nearest .vault, .vault_pass, .vault_pass.txt or .vault-password above the file, and prompts as a last resort. A password file found that way is used only if you own it, it is not executable and no one else can write to it, because ansible-vault runs an executable password file. Decryption is refused when Git tracks the file or does not ignore it; vault-toggle --force FILE overrides that from a terminal.

GitLab publishes standalone configuration archives for both editors. Back up your current configuration first: extraction replaces files with the same names.

Install the Neovim configuration on its own The archive installs `~/.config/nvim`.
set -eu
archive=$(mktemp)
trap 'rm -f -- "$archive"' EXIT
curl -fL 'https://gitlab.com/pad92/dotfiles/-/jobs/artifacts/main/raw/nvim.tar.gz?job=package_nvim' -o "$archive"
tar -tzf "$archive"
read -r -p 'Extract this archive into your home directory? [y/N] ' confirm
[ "$confirm" = y ] || exit 1
tar -xzf "$archive" -C "$HOME"
Install the Vim configuration on its own The archive installs `~/.vim` and `~/.vimrc` with the plugins, at the commits the role pins, under `~/.vim/pack/dotfiles/start/`. Vim loads them at startup, so there is nothing else to install. [`scripts/vim.sh`](./scripts/vim.sh) does the same download and extraction without the confirmation step.
set -eu
archive=$(mktemp)
trap 'rm -f -- "$archive"' EXIT
curl -fL 'https://gitlab.com/pad92/dotfiles/-/jobs/artifacts/main/raw/vim.tar.gz?job=package_vim' -o "$archive"
tar -tzf "$archive"
read -r -p 'Extract this archive into your home directory? [y/N] ' confirm
[ "$confirm" = y ] || exit 1
tar -xzf "$archive" -C "$HOME"

Scripts

The playbook deploys ~/.bin/ and Zsh adds it to PATH.

ScriptPurpose
steam-optimizeConfigure per-game Vulkan and Gamescope options.
backup.shBack up files with rsync over SSH.
awww.shSelect and render wallpapers.
razer_dpi.pyConfigure Razer mouse DPI.
hypr-screenshot.shRecord the desktop still and animated demo.
hypr-record.shRecord a screen or a window as a light MP4.
hypr-keybinds.shList the loaded keybindings (SUPER + F1).
comcut, comskip.shDetect and remove commercial breaks.
diff-cmdCompare command output for two arguments.
vscodium_ext.shInstall VSCodium extensions.
vault-toggleEncrypt or decrypt a file with Ansible Vault.

backup.sh only cleans package caches when you pass --clean-packages: it then runs yay -Sc on Arch or apt autoremove --purge on Debian.

Development

CommandWhat it does
make verifyRun make lint, then make test.
make testRun the Python and shell test suites. Set JOBS=N to limit parallelism.
make coverageRun the Python tests under coverage.py and enforce the .coveragerc floor.
make lint-ansibleRun ansible-lint, yamllint, and the playbook syntax check.
make screenshotRender the desktop showcase at the top of this page (RES, SETTLE, ...).

The pre-commit hook runs make coverage and the shell tests, as the CI does, so a commit that lowers the coverage below the .coveragerc floor is refused locally. It needs coverage.py (python-coverage, installed with the 10_sre_devops packages).

GitLab, GitHub, and Gitea share the scripts in scripts/ci/; see the GitHub workflows and GitLab CI documentation. Pushing a v* tag publishes a release with the matching changelog section. The repository is mirrored on GitLab, GitHub, and Codeberg.

Maintained by pad🐐 since 2015.