commit e1cefdcd26d535b6e57a737dd9ffa8eed94e40b1 Author: Alexander Hess Date: Thu Jun 11 18:28:00 2026 +0200 Add base configuration - Follow XDG standard: ~/.config and ~/.local folders - Set environment variables and define aliases within ~/.config/shell - Add installation script for easy setup and maintenance (i.e., `install-dotfiles` and `update-dotfiles`, and a `ensure-locales` helper) - Add README.md with info on the installation and general notes diff --git a/.bashrc b/.bashrc new file mode 100644 index 0000000..edfef4e --- /dev/null +++ b/.bashrc @@ -0,0 +1,14 @@ +#!/bin/bash + +# `bash`-specific configurations + + +# Prevent running this file twice, as a non-login `bash` enters here, +# sources ~/.profile, and ~/.profile sources this file again +[ -n "$BASHRC_LOADED" ] && return +BASHRC_LOADED=1 + + +# Load configuration files common to all kinds of shells, +# if not already done by a `bash` login shell +[ -z "$PROFILE_LOADED" ] && [ -f "$HOME/.profile" ] && . "$HOME/.profile" diff --git a/.config/shell/README.md b/.config/shell/README.md new file mode 100644 index 0000000..627d36a --- /dev/null +++ b/.config/shell/README.md @@ -0,0 +1,11 @@ +# Shell-related Configurations + +This folder contains files that are sourced by `bash` and `zsh`. + +They are not executable. +The `#!/bin/sh` line documents the dialect for readers and editors; + it has no effect, since the files are sourced and never run directly. + +[`welcome`](./welcome) and [`logout`](./logout) + depend on helper functions defined in [`~/.profile`](../../.profile), + so they cannot work standalone. diff --git a/.config/shell/aliases b/.config/shell/aliases new file mode 100644 index 0000000..cc2c438 --- /dev/null +++ b/.config/shell/aliases @@ -0,0 +1,10 @@ +#!/bin/sh + +# Aliases used in all kinds of shells + + +# Manage the bare `git` repository in ~/ holding the dotfiles +alias dotfiles='git --git-dir=$DOTFILES_DIR --work-tree=$HOME' + + +alias grep='grep --exclude-dir=.git' diff --git a/.config/shell/env b/.config/shell/env new file mode 100644 index 0000000..5829ec4 --- /dev/null +++ b/.config/shell/env @@ -0,0 +1,38 @@ +#!/bin/sh + +# Environment variables for all kinds of shells + + +# Standard XDG base directories +# See: https://wiki.archlinux.org/title/XDG_Base_Directory + +export XDG_CACHE_HOME="$HOME/.cache" +export XDG_CONFIG_HOME="$HOME/.config" +export XDG_DATA_HOME="$HOME/.local/share" # also set in ~/.local/bin/dotfiles-{install,update} +export XDG_STATE_HOME="$HOME/.local/state" + +# Make up a XDG directory for binaries (that does not exist in the standard) +export XDG_BIN_HOME="$HOME/.local/bin" + + +# Convenient names for various places in the system + +export DOTFILES_DIR="$XDG_DATA_HOME/dotfiles" # also set in ~/.local/bin/dotfiles-{install,update} + + +# Generic shell configs + + +[ -t 0 ] && export GPG_TTY=$(tty) + + +export PAGER=less +export LESS="--chop-long-lines --ignore-case --LONG-PROMPT --no-init --raw-control-chars --status-column --quit-if-one-screen" + + +[ -f "$XDG_CONFIG_HOME/shell/locale" ] && . "$XDG_CONFIG_HOME/shell/locale" + + +# Move common tools' config and cache files into XDG directories + +export LESSHISTFILE="$XDG_STATE_HOME/less/history" diff --git a/.config/shell/locale b/.config/shell/locale new file mode 100644 index 0000000..7c4fd27 --- /dev/null +++ b/.config/shell/locale @@ -0,0 +1,24 @@ +#!/bin/sh + +# Locale settings for headless machines + + +# Drop what a client may forward over `ssh`, +# because `AcceptEnv LANG LC_*` may be set in /etc/ssh/sshd_config +unset \ + LC_ALL \ + LC_ADDRESS \ + LC_COLLATE \ + LC_CTYPE LC_IDENTIFICATION \ + LC_MEASUREMENT \ + LC_MESSAGES \ + LC_MONETARY \ + LC_NAME \ + LC_NUMERIC \ + LC_PAPER \ + LC_TELEPHONE \ + LC_TIME + + +# Set every LC_* category implicitly with the `$LANG` fallback +export LANG=C.UTF-8 diff --git a/.local/bin/README.md b/.local/bin/README.md new file mode 100644 index 0000000..2fe7197 --- /dev/null +++ b/.local/bin/README.md @@ -0,0 +1,3 @@ +# User-local Executables + +This folder contains executable files that are on the `$PATH`. diff --git a/.local/bin/dotfiles-install b/.local/bin/dotfiles-install new file mode 100755 index 0000000..8cd50c3 --- /dev/null +++ b/.local/bin/dotfiles-install @@ -0,0 +1,115 @@ +#!/bin/sh + +# Installation script to make the dotfiles available in ~/ +# +# `git` is the only dependency for this script to run (See: https://git-scm.com) +# +# See: https://code.webartifex.de/alexander/dotfiles#installation + + +set -eu + + +XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}" # also set in ~/.config/shell/env +XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}" # also set in ~/.config/shell/env + +DOTFILES_BRANCH="main" +DOTFILES_DIR="$XDG_DATA_HOME/dotfiles" # also set in ~/.config/shell/env +DOTFILES_HTTPS="https://code.webartifex.de/alexander/dotfiles" +DOTFILES_SSH="git@git.webartifex.de:alexander/dotfiles.git" + +BACKUPS_DIR="$XDG_STATE_HOME/dotfiles-backups" +BACKUP_DIR="$BACKUPS_DIR/$(date +%Y%m%d-%H%M%S)" + + +if [ -d "$DOTFILES_DIR" ]; then + + [ "${1:-}" = "--force" ] && force=1 || force=0 + + if [ "$force" -ne 1 ]; then + + echo "" + echo "The dotfiles are already installed at: $DOTFILES_DIR" + echo "" + echo "Options:" + echo " - Run the installation again with '--force'" + echo " - Update with 'dotfiles pull' and continue using them" + echo "" + + exit 0 + + fi + + echo "Backing up the existing repository to: $BACKUP_DIR" + mkdir -p "$BACKUP_DIR" + mv "$DOTFILES_DIR" "$BACKUP_DIR/dotfiles" + +fi + + +git clone --bare "$DOTFILES_HTTPS" "$DOTFILES_DIR" + + +_git() { + git --git-dir="$DOTFILES_DIR" --work-tree="$HOME" "$@" +} + + +if ! _git show-ref --verify --quiet "refs/heads/$DOTFILES_BRANCH"; then + + echo "" >&2 + echo "Internal error: branch '$DOTFILES_BRANCH' missing in the repository" >&2 + echo "" >&2 + echo "Available branches:" >&2 + _git branch --format=' %(refname:short)' >&2 + echo "" >&2 + + exit 1 + +fi + + +_git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*' +_git fetch origin + + +# Do not checkout project documentation intended for web GUIs +_git config core.sparseCheckout true +{ + echo "/*" + echo "!/LICENSE.txt" + echo "!/README.md" +} > "$DOTFILES_DIR/info/sparse-checkout" + + +# Back up files in the user's $HOME folder if a checkout is about to overwrite them +_conflicts=$(_git ls-tree -r --name-only "$DOTFILES_BRANCH" | while IFS= read -r f; do + if [ -e "$HOME/$f" ] || [ -L "$HOME/$f" ]; then echo "$f"; fi +done) + +if [ -n "$_conflicts" ]; then + + echo "Backing up pre-existing files to: $BACKUP_DIR" + echo "$_conflicts" | while IFS= read -r f; do + mkdir -p "$BACKUP_DIR/$(dirname "$f")" + cp -a "$HOME/$f" "$BACKUP_DIR/$f" + done + +fi + +# Put the dotfiles in the user's $HOME folder +_git checkout --force "$DOTFILES_BRANCH" + +# Do not show files not tracked in the dotfiles repository because there are simply too many +_git config --local status.showUntrackedFiles no + +# Prefer `ssh` for syncing between the machines +_git remote set-url origin "$DOTFILES_SSH" + + +echo "" +echo "The dotfiles were installed successfully (branch: $DOTFILES_BRANCH)" +echo "" +echo "Reload your shell to use them:" +echo " exec \$SHELL -l" +echo "" diff --git a/.local/bin/dotfiles-update b/.local/bin/dotfiles-update new file mode 100755 index 0000000..60eb62a --- /dev/null +++ b/.local/bin/dotfiles-update @@ -0,0 +1,87 @@ +#!/bin/sh + +# Replace the local `dotfiles` repository with a fresh install +# by downloading and re-running the latest `dotfiles-install` +# +# Refuses to run if there are uncommitted or unpushed changes, +# unless called with `--force` + +set -eu + + +XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}" # also set in ~/.config/shell/env +DOTFILES_DIR="$XDG_DATA_HOME/dotfiles" # also set in ~/.config/shell/env + + +if [ -d "$DOTFILES_DIR" ]; then + + _git() { + git --git-dir="$DOTFILES_DIR" --work-tree="$HOME" "$@" + } + + # Keep the machine on its current branch (e.g., "desktop") + branch=$(_git symbolic-ref --short HEAD) || { + echo "" >&2 + echo "Detached HEAD => re-run after 'dotfiles checkout '" >&2 + echo "" >&2 + + exit 1 + } + + [ "${1:-}" = "--force" ] && force=1 || force=0 + + if [ "$force" -ne 1 ]; then + + if [ -n "$(_git status --porcelain)" ]; then + echo "" >&2 + echo "There are uncommitted changes!" >&2 + echo "" >&2 + _git --no-pager diff HEAD --stat >&2 + echo "" >&2 + echo "=> Commit and push them, or re-run with --force" >&2 + echo "" >&2 + + exit 1 + fi + + if _git rev-parse --verify --quiet "origin/$branch" >/dev/null \ + && [ -n "$(_git log --oneline "origin/$branch..$branch")" ]; then + echo "" >&2 + echo "There are unpushed commits!" >&2 + echo "" >&2 + _git --no-pager log --oneline "origin/$branch..$branch" >&2 + echo "" >&2 + echo "=> Push them, or re-run with --force" >&2 + echo "" >&2 + + exit 1 + fi + + fi + +else + branch="main" +fi + + +installer_url="https://code.webartifex.de/alexander/dotfiles/raw/branch/$branch/.local/bin/dotfiles-install" + +installer_path=$(mktemp) +trap 'rm -f "$installer_path"' EXIT + + +if command -v curl >/dev/null 2>&1; then + curl -fsSL "$installer_url" -o "$installer_path" +elif command -v wget >/dev/null 2>&1; then + wget -q "$installer_url" -O "$installer_path" +else + echo "" >&2 + echo "Neither curl nor wget are installed!" >&2 + echo "=> Install one of them" >&2 + echo "" >&2 + + exit 1 +fi + + +sh "$installer_path" --force diff --git a/.local/state/less/.gitkeep b/.local/state/less/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.profile b/.profile new file mode 100644 index 0000000..7861e6f --- /dev/null +++ b/.profile @@ -0,0 +1,54 @@ +#!/bin/sh + +# Main setup file executed for all kinds of shells + + +# Prevent loading ~/.profile twice in `bash` +export PROFILE_LOADED=1 + + +# Basic utilities + +_command_exists() { + command -v "$1" 1>/dev/null 2>&1 +} + +_in_bash() { + [ -n "$BASH_VERSION" ] +} + +_in_zsh() { + [ -n "$ZSH_VERSION" ] +} + +_prepend_to_path () { + if [ -d "$1" ] ; then + case :$PATH: in + *:$1:*) ;; + *) PATH=$1:$PATH ;; + esac + fi +} + + +_prepend_to_path "$HOME/.local/bin" + + +[ -f "$HOME/.config/shell/env" ] && . "$HOME/.config/shell/env" + + +[ -f "$HOME/.config/shell/aliases" ] && . "$HOME/.config/shell/aliases" + + +# Source ~/.profile_local, which holds machine-specific ENV variables +[ -f "$HOME/.profile_local" ] && . "$HOME/.profile_local" + + +# Load `bash`-specific configurations for non-login `bash` shells +if [ -n "$BASH_VERSION" ] && [ -f "$HOME/.bashrc" ]; then + . "$HOME/.bashrc" +fi + + +# Ensure ~/.profile is loaded each time `bash` starts +unset PROFILE_LOADED diff --git a/.zshrc b/.zshrc new file mode 100644 index 0000000..3a46429 --- /dev/null +++ b/.zshrc @@ -0,0 +1,7 @@ +#!/bin/zsh + +# `zsh`-specific configurations + + +# Load configuration files common to all kinds of shells +[ -f "$HOME/.profile" ] && . "$HOME/.profile" diff --git a/README.md b/README.md new file mode 100644 index 0000000..d3ae4b5 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# Dotfiles + +This repository contains useful (config) files. + +There are two branches: +- [*main*](https://code.webartifex.de/alexander/dotfiles/src/branch/main) (~ "headless") +- [*desktop*](https://code.webartifex.de/alexander/dotfiles/src/branch/desktop) (for GNOME 48+ on X11) + +`main` contains dotfiles intended to be used on all kinds of machines + and can be thought of as a "minimal" or "server" version. +It targets [Debian](https://www.debian.org/) 13 + but is kept working on older versions and other distributions as well. +`desktop` is (re-)based on top of `main` + and adds configurations for + [GNOME](https://www.gnome.org/) 48+ + on [X11](https://www.x.org/) + and many applications, + be it end-user software or common development utilities. + + +## Installation + +Simply run: + +```sh +curl https://code.webartifex.de/alexander/dotfiles/raw/branch/main/.local/bin/dotfiles-install > dotfiles-install && sh ./dotfiles-install && rm ./dotfiles-install +``` + +or + +```sh +wget https://code.webartifex.de/alexander/dotfiles/raw/branch/main/.local/bin/dotfiles-install -O dotfiles-install && sh ./dotfiles-install && rm ./dotfiles-install +``` + +This downloads a simple [installation script](.local/bin/dotfiles-install) + and then executes it. +The script's only dependency is [`git`](https://git-scm.com/). +So, it should not be too hard to get this going. + +The repository is cloned as a bare repository into `~/.local/share/dotfiles` + and its files are checked out into `~/` directly. +Pre-existing files are backed up to `~/.local/state/dotfiles-backups` beforehand. +Afterwards, the files can be managed with the + [`dotfiles`](.config/shell/aliases#L7) alias, + for example, `dotfiles status` or `dotfiles pull`. + +The above commands install the *main* variant. +To install the *desktop* variant, + switch to the [desktop](https://code.webartifex.de/alexander/dotfiles/src/branch/desktop) branch first. + +Normally, I advise against executing shell scripts from the internet, + but this one is short enough to be read even by beginners. +So, convince yourself that it is not harmful! + + +## Shells + +The config files in this repository are optimized for usage with + [GNU's Bourne again shell](https://man7.org/linux/man-pages/man1/bash.1.html), + or `bash` for short, + and the popular [zsh](https://www.zsh.org/).