From bab6901df8bbd9674d7adc0dea8818238d7b873b Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Fri, 22 May 2026 15:52:24 -0500 Subject: feat(archsetup): clone dotfiles repo and stow per DESKTOP_ENV MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dotfiles now live in their own repo (git.cjennings.net/dotfiles.git), so archsetup clones DOTFILES_REPO to the user's ~/.dotfiles and stows the right tree for the chosen DESKTOP_ENV: dwm and hyprland get common/ plus their own layer, none gets the standalone minimal/ tree. The clone runs as the target user, so the working tree is user-owned with no chown-after race. Add DOTFILES_REPO / DOTFILES_BRANCH / DOTFILES_DIR config keys with the same injection guard the other repo keys carry. If the clone doesn't produce a git checkout, error_fatal stops the install rather than silently skipping the restore step that reverts what stow --adopt pulled in. That restore now runs for every DESKTOP_ENV, including none — minimal/ ships the .bashrc/.bash_profile that collide with /etc/skel, so its --adopt needs the same cleanup. --- archsetup | 101 +++++++++++++++++++++++++++++++++---------------- archsetup.conf.example | 16 ++++++++ 2 files changed, 84 insertions(+), 33 deletions(-) diff --git a/archsetup b/archsetup index 6f828f5..156af27 100755 --- a/archsetup +++ b/archsetup @@ -118,6 +118,9 @@ load_config() { [[ -n "$SLOCK_REPO" ]] && slock_repo="$SLOCK_REPO" [[ -n "$DOTEMACS_REPO" ]] && dotemacs_repo="$DOTEMACS_REPO" [[ -n "$ARCHSETUP_REPO" ]] && archsetup_repo="$ARCHSETUP_REPO" + [[ -n "$DOTFILES_REPO" ]] && dotfiles_repo="$DOTFILES_REPO" + [[ -n "$DOTFILES_BRANCH" ]] && dotfiles_branch="$DOTFILES_BRANCH" + [[ -n "$DOTFILES_DIR" ]] && dotfiles_dir="$DOTFILES_DIR" [[ -n "$LOCALE" ]] && locale="$LOCALE" [[ -n "$DESKTOP_ENV" ]] && desktop_env="$DESKTOP_ENV" } @@ -180,7 +183,7 @@ validate_config() { # leading dash, which git would parse as an option -- plus whitespace and # control characters. local repo - for repo in "$dwm_repo" "$dmenu_repo" "$st_repo" "$slock_repo" "$dotemacs_repo" "$archsetup_repo"; do + for repo in "$dwm_repo" "$dmenu_repo" "$st_repo" "$slock_repo" "$dotemacs_repo" "$archsetup_repo" "$dotfiles_repo"; do [[ -z "$repo" ]] && continue if [[ "$repo" == -* || "$repo" =~ [[:space:][:cntrl:]] ]]; then echo "ERROR: Repository spec must not start with '-' or contain whitespace/control characters: '$repo'" >&2 @@ -203,9 +206,6 @@ password="${password:-}" # prompted if not set locale="${locale:-}" # set via prompt if not configured desktop_env="${desktop_env:-hyprland}" # options: dwm, hyprland, none -archsetup_dir="$(dirname "$(readlink -f "$0")")" -dotfiles_dir="$archsetup_dir/dotfiles" - # Git repositories for suckless tools and dotfiles # Override these in config file to use your own forks dwm_repo="${dwm_repo:-https://git.cjennings.net/dwm.git}" @@ -215,6 +215,12 @@ slock_repo="${slock_repo:-https://git.cjennings.net/slock.git}" dotemacs_repo="${dotemacs_repo:-https://git.cjennings.net/dotemacs.git}" archsetup_repo="${archsetup_repo:-https://git.cjennings.net/archsetup.git}" +# Dotfiles live in their own repo, cloned to $dotfiles_dir and stowed at install +# time. $dotfiles_dir defaults to the user's ~/.dotfiles, set once $username is +# known (see user_customizations). +dotfiles_repo="${dotfiles_repo:-https://git.cjennings.net/dotfiles.git}" +dotfiles_branch="${dotfiles_branch:-main}" + logfile="/var/log/archsetup-$(date +'%Y-%m-%d-%H-%M-%S').log" source_dir="" # set in preflight_checks after username is known packages_before="/var/log/archsetup-preexisting-package-list.txt" @@ -954,29 +960,54 @@ user_customizations() { chown -R "$username": "/home/$username/code") \ >> "$logfile" 2>&1 || error_warn "$action" "$?" - # Update dotfiles_dir to point to user-accessible location - dotfiles_dir="$user_archsetup_dir/dotfiles" - - action="linking dotfiles into place" && display "task" "$action" - (cd "$dotfiles_dir" && stow --target="/home/$username" --no-folding --adopt common \ - >> "$logfile" 2>&1 ) || error_warn "$action" "$?" + # Clone the dotfiles repo (separate from archsetup) and stow from it. + # Defaults to ~/.dotfiles; override via DOTFILES_DIR. + dotfiles_dir="${dotfiles_dir:-/home/$username/.dotfiles}" + action="cloning dotfiles repo" && display "task" "$action" + # Clone as root, then hand the tree to the user. useradd -m skips the home-dir + # chown when /home/$username already exists (pre-seeded images, re-runs), which + # leaves /home/$username root-owned — so a clone running as the user fails with + # "Permission denied" creating ~/.dotfiles. Cloning as root sidesteps that, and + # chown -R gives the user the working tree. Mirrors the archsetup clone above. + (git clone --depth 1 --branch "$dotfiles_branch" "$dotfiles_repo" "$dotfiles_dir" \ + && chown -R "$username": "$dotfiles_dir") >> "$logfile" 2>&1 || error_warn "$action" "$?" + + # Q5: the --adopt/restore conflict handling below needs a real git checkout. + # Refuse to continue if the clone didn't produce one (bad URL, network, a + # tarball drop) rather than silently skipping the restore step. + [[ -d "$dotfiles_dir/.git" ]] || error_fatal "dotfiles dir is not a git checkout: $dotfiles_dir" 1 + + # root runs stow/restore against the user-owned clone; mark it safe. + git config --global --add safe.directory "$dotfiles_dir" >> "$logfile" 2>&1 || true + + # Stow the universal layer plus the per-environment layer. Headless installs + # (none) get the standalone minimal/ tree instead of common/. + case "$desktop_env" in + dwm|hyprland) + action="linking common dotfiles" && display "task" "$action" + (cd "$dotfiles_dir" && stow --target="/home/$username" --no-folding --adopt common \ + >> "$logfile" 2>&1) || error_warn "$action" "$?" + action="linking $desktop_env dotfiles" && display "task" "$action" + (cd "$dotfiles_dir" && stow --target="/home/$username" --no-folding --adopt "$desktop_env" \ + >> "$logfile" 2>&1) || error_warn "$action" "$?" + ;; + none) + action="linking minimal dotfiles" && display "task" "$action" + (cd "$dotfiles_dir" && stow --target="/home/$username" --no-folding --adopt minimal \ + >> "$logfile" 2>&1) || error_warn "$action" "$?" + ;; + esac - # Stow desktop-environment-specific dotfiles - if [[ "$desktop_env" == "hyprland" ]]; then - action="linking hyprland dotfiles" && display "task" "$action" - (cd "$dotfiles_dir" && stow --target="/home/$username" --no-folding --adopt hyprland \ - >> "$logfile" 2>&1 ) || error_warn "$action" "$?" - - # Remove battery module from waybar config on desktops (no battery) - if ! ls /sys/class/power_supply/BAT* &>/dev/null; then - action="removing waybar battery module (no battery detected)" && display "task" "$action" - waybar_config="/home/$username/.config/waybar/config" - # Remove "battery" from sysmonitor modules array and fix trailing comma - sed -i '/"battery"$/d' "$waybar_config" - sed -i 's/"custom\/disk",/"custom\/disk"/' "$waybar_config" - # Remove the battery config block - sed -i '/"battery": {/,/^ },$/d' "$waybar_config" - fi + # Remove battery module from waybar config on desktops with no battery + # (hyprland only — waybar isn't part of the dwm or minimal trees). + if [[ "$desktop_env" == "hyprland" ]] && ! ls /sys/class/power_supply/BAT* &>/dev/null; then + action="removing waybar battery module (no battery detected)" && display "task" "$action" + waybar_config="/home/$username/.config/waybar/config" + # Remove "battery" from sysmonitor modules array and fix trailing comma + sed -i '/"battery"$/d' "$waybar_config" + sed -i 's/"custom\/disk",/"custom\/disk"/' "$waybar_config" + # Remove the battery config block + sed -i '/"battery": {/,/^ },$/d' "$waybar_config" fi # install fontconfig before refreshing cache (provides fc-cache) @@ -1022,13 +1053,17 @@ EOF dconf update ) >> "$logfile" 2>&1 || error_warn "$action" "$?" - action="marking dotfile dir as safe.directory" && display "task" "$action" - if git config --global --add safe.directory "$user_archsetup_dir" >> "$logfile" 2>&1; then - action="restoring dotfile versions" && display "task" "$action" - git -C "$dotfiles_dir" restore . >> "$logfile" 2>&1 || error_warn "$action" "$?" - else - error_warn "marking dotfile dir as safe.directory" "$?" - fi + action="marking archsetup dir as safe.directory" && display "task" "$action" + git config --global --add safe.directory "$user_archsetup_dir" >> "$logfile" 2>&1 \ + || error_warn "$action" "$?" + + # Revert what stow --adopt pulled into the dotfiles working tree, so the + # symlinks resolve to the repo's versions rather than any pre-existing files + # (e.g. the /etc/skel .bashrc/.bash_profile a fresh user starts with). Runs + # for every desktop_env, including none — minimal/ ships those skel-colliding + # files too, so its --adopt needs the same restore. + action="restoring dotfile versions" && display "task" "$action" + git -C "$dotfiles_dir" restore . >> "$logfile" 2>&1 || error_warn "$action" "$?" action="creating common directories" && display "task" "$action" # Create default directories and grant permissions diff --git a/archsetup.conf.example b/archsetup.conf.example index fe8cd26..f866134 100644 --- a/archsetup.conf.example +++ b/archsetup.conf.example @@ -55,3 +55,19 @@ #SLOCK_REPO=https://github.com/yourusername/slock.git #DOTEMACS_REPO=https://github.com/yourusername/dotemacs.git #ARCHSETUP_REPO=https://github.com/yourusername/archsetup.git + +############################# +# Dotfiles +############################# +# Dotfiles live in their own repo, cloned to DOTFILES_DIR and stowed at install +# time. The repo must contain a common/ subdir plus dwm/, hyprland/, and/or +# minimal/ subdirs that stow cleanly to ~. DESKTOP_ENV picks which: +# dwm -> common/ + dwm/ +# hyprland -> common/ + hyprland/ +# none -> minimal/ only +#DOTFILES_REPO=https://git.cjennings.net/dotfiles.git +#DOTFILES_BRANCH=main +# DOTFILES_DIR: leave unset to use the target user's ~/.dotfiles (recommended). +# If you set it, use a literal absolute path under the user's home — this file is +# sourced as root, so $HOME here is /root, not the user's home. +#DOTFILES_DIR=/home/youruser/.dotfiles -- cgit v1.2.3