diff options
| author | Craig Jennings <c@cjennings.net> | 2026-01-24 13:21:01 -0600 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-01-24 13:21:01 -0600 |
| commit | 6011f90328d88a2c449442d6a31fef3614926ec9 (patch) | |
| tree | 0d278c1a7ca11d232a7293bae5b2b95a0dad1f01 | |
| parent | 27595e0dc40c2e8daa78d2f057f6f64dc229adc5 (diff) | |
fix(archsetup): bug fixes, locale support, and code improvements
Bug fixes:
- Initialize errors_encountered at script start (not in intro)
- Capture correct exit code in retry_install loop
- Add missing error_fatal parameters
- Fix unclosed quote in error message
- Quote variables in pacman_install/aur_install commands
- Standardize done statements (remove trailing semicolons)
New features:
- Locale selection prompt with 8 common options + custom entry
- Auto-derive wireless region from locale
- Extract zfs-replicate to separate script file
- Make archsetup repo URL configurable
- Add MulticastDNS=no to avoid avahi conflict
Code improvements:
- Single STEPS array for show_status and main execution loop
- Document security note for config file sourcing
- Add explanatory comment for UFW VM behavior
- Silence update-desktop-database warnings
Config updates:
- Add LOCALE and ARCHSETUP_REPO to example config
Also adds Wayland/Hyprland desktop alternative to V2MOM roadmap.
| -rwxr-xr-x | archsetup | 213 | ||||
| -rw-r--r-- | archsetup.conf.example | 6 | ||||
| -rw-r--r-- | assets/2026-01-23-avahi-mdns-fixes.org | 125 | ||||
| -rwxr-xr-x | scripts/zfs-replicate | 72 | ||||
| -rw-r--r-- | todo.org | 55 |
5 files changed, 356 insertions, 115 deletions
@@ -98,6 +98,9 @@ load_config() { exit 1 fi echo "Loading config from: $config_path" + # SECURITY NOTE: source executes config as bash. Only use trusted config files. + # This is acceptable because: (1) user explicitly provides path, (2) script runs + # as root anyway, (3) if attacker can write config, they likely have root access. # shellcheck disable=SC1090 source "$config_path" @@ -114,6 +117,8 @@ load_config() { [[ -n "$ST_REPO" ]] && st_repo="$ST_REPO" [[ -n "$SLOCK_REPO" ]] && slock_repo="$SLOCK_REPO" [[ -n "$DOTEMACS_REPO" ]] && dotemacs_repo="$DOTEMACS_REPO" + [[ -n "$ARCHSETUP_REPO" ]] && archsetup_repo="$ARCHSETUP_REPO" + [[ -n "$LOCALE" ]] && locale="$LOCALE" } # Load config if specified @@ -124,6 +129,7 @@ load_config() { username="${username:-cjennings}" password="${password:-welcome}" # CHANGE ON FIRST LOGIN +locale="${locale:-}" # set via prompt if not configured archsetup_dir="$(dirname "$(readlink -f "$0")")" dotfiles_dir="$archsetup_dir/dotfiles" @@ -135,6 +141,7 @@ dmenu_repo="${dmenu_repo:-https://git.cjennings.net/dmenu.git}" st_repo="${st_repo:-https://git.cjennings.net/st.git}" 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}" logfile="/var/log/archsetup-$(date +'%Y-%m-%d-%H-%M-%S').log" source_dir="/home/$username/.local/src" # aur/git source goes here @@ -145,6 +152,12 @@ archsetup_packages="/var/log/archsetup-installed-packages.txt" min_disk_space_gb=20 state_dir="/var/lib/archsetup/state" error_messages=() +errors_encountered=0 + +# Installation steps (single source of truth for show_status and main execution) +STEPS=(intro prerequisites create_user user_customizations aur_installer + essential_services xorg dwm desktop_environment developer_workstation + supplemental_software boot_ux) ### Cleanup Trap # Ensures tmpfs is unmounted if script exits unexpectedly @@ -198,10 +211,7 @@ show_status() { exit 0 fi echo "Completed steps:" - for step in intro prerequisites create_user user_customizations \ - aur_installer essential_services xorg dwm \ - desktop_environment developer_workstation \ - supplemental_software boot_ux; do + for step in "${STEPS[@]}"; do if step_completed "$step"; then timestamp=$(cat "$state_dir/$step") printf " [x] %-25s (%s)\n" "$step" "$timestamp" @@ -265,14 +275,49 @@ preflight_checks() { fi echo " [OK] System: Arch Linux detected" + # Check locale configuration + if grep -q "^LANG=" /etc/locale.conf 2>/dev/null; then + current_locale=$(grep "^LANG=" /etc/locale.conf | cut -d= -f2) + echo " [OK] Locale: $current_locale" + elif [[ -n "$locale" ]]; then + echo " [OK] Locale: $locale (from config)" + else + echo "" + echo "Locale not configured. Please select:" + echo " 1) en_US.UTF-8 (US English)" + echo " 2) en_GB.UTF-8 (UK English)" + echo " 3) de_DE.UTF-8 (German)" + echo " 4) es_ES.UTF-8 (Spanish)" + echo " 5) fr_FR.UTF-8 (French)" + echo " 6) pt_BR.UTF-8 (Brazilian Portuguese)" + echo " 7) ja_JP.UTF-8 (Japanese)" + echo " 8) zh_CN.UTF-8 (Chinese)" + echo " 9) Other (enter manually)" + read -r -p "Choice [1]: " choice + case "${choice:-1}" in + 1) locale="en_US.UTF-8" ;; + 2) locale="en_GB.UTF-8" ;; + 3) locale="de_DE.UTF-8" ;; + 4) locale="es_ES.UTF-8" ;; + 5) locale="fr_FR.UTF-8" ;; + 6) locale="pt_BR.UTF-8" ;; + 7) locale="ja_JP.UTF-8" ;; + 8) locale="zh_CN.UTF-8" ;; + 9) + read -r -p "Enter locale (e.g., nl_NL.UTF-8): " locale + [[ -z "$locale" ]] && locale="en_US.UTF-8" + ;; + *) locale="en_US.UTF-8" ;; + esac + echo " [OK] Locale: $locale (selected)" + fi + echo "Pre-flight checks passed." echo "" } ### Intro intro() { - errors_encountered=0 - # begin with a clean logfile [ -f "$logfile" ] && rm -f "$logfile" touch "$logfile" @@ -334,28 +379,30 @@ retry_install() { local source="$2" local cmd="$3" local attempt=1 + local last_exit_code=0 display "task" "installing $pkg via $source" while [ $attempt -le $MAX_INSTALL_RETRIES ]; do if eval "$cmd" >> "$logfile" 2>&1; then return 0 fi + last_exit_code=$? attempt=$((attempt + 1)) if [ $attempt -le $MAX_INSTALL_RETRIES ]; then display "task" "retrying $pkg (attempt $attempt/$MAX_INSTALL_RETRIES)" fi done - error_warn "$pkg ($source)" "$?" + error_warn "$pkg ($source)" "$last_exit_code" } # Pacman Install pacman_install() { - retry_install "$1" "pacman" "pacman --noconfirm --needed -S $1" + retry_install "$1" "pacman" "pacman --noconfirm --needed -S \"$1\"" } # AUR Install aur_install() { - retry_install "$1" "AUR" "sudo -u $username yay -S --noconfirm $1" + retry_install "$1" "AUR" "sudo -u \"$username\" yay -S --noconfirm \"$1\"" } # Git Install @@ -600,11 +647,19 @@ prerequisites() { display "subtitle" "Environment Configuration" # configure locale (must happen before package installs that depend on locale) - action="configuring locale" && display "task" "$action" - sed -i 's/^#en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen - (locale-gen >> "$logfile" 2>&1) || error_warn "$action" "$?" - echo "LANG=en_US.UTF-8" > /etc/locale.conf - export LANG=en_US.UTF-8 + # Skip if already configured by installer + if ! grep -q "^LANG=" /etc/locale.conf 2>/dev/null; then + action="configuring locale ($locale)" && display "task" "$action" + # Uncomment the selected locale in locale.gen (format: "en_US.UTF-8 UTF-8") + locale_entry="${locale} ${locale##*.}" # e.g., "en_US.UTF-8 UTF-8" + sed -i "s/^#${locale_entry}/${locale_entry}/" /etc/locale.gen + (locale-gen >> "$logfile" 2>&1) || error_warn "$action" "$?" + echo "LANG=$locale" > /etc/locale.conf + export LANG="$locale" + else + display "task" "locale already configured ($(grep ^LANG= /etc/locale.conf))" + export LANG="$(grep ^LANG= /etc/locale.conf | cut -d= -f2)" + fi # sync the time on this machine (one-shot chrony sync) action="synchronizing system time" && display "task" "$action" @@ -663,7 +718,7 @@ EOF [ -f /etc/sudoers.pacnew ] && cp /etc/sudoers.pacnew /etc/sudoers >> "$logfile" 2>&1 action="creating a directory to build/install software from git/AUR." - (mkdir -p "$source_dir") || error_fatal "creating the directory $source_dir" + (mkdir -p "$source_dir") || error_fatal "creating the directory $source_dir" "$?" } @@ -674,15 +729,16 @@ create_user() { display "task" "checking if user exists" # halt if $username exists ( id -u "$username" >/dev/null 2>&1; ) && \ - error_fatal "user '$username' already exists!" + error_fatal "user '$username' already exists" "user exists" # create $username with home, group, shell, password action="creating user and home directory" && display "task" "$action" (useradd -m -G wheel -s /bin/zsh "$username" >> "$logfile" 2>&1) || \ - error_fatal "adding user '$username" "$?" + error_fatal "adding user '$username'" "$?" display "task" "assigning the password" echo "$username:$password" | chpasswd # any text is allowable! be careful! + unset password # clear from memory after use display "task" "adding to appropriate groups" (usermod -aG \ @@ -726,7 +782,7 @@ user_customizations() { user_archsetup_dir="/home/$username/code/archsetup" action="cloning archsetup to user's home directory" && display "task" "$action" (mkdir -p "$(dirname "$user_archsetup_dir")" && \ - git clone --depth 1 https://git.cjennings.net/archsetup.git "$user_archsetup_dir" && \ + git clone --depth 1 "$archsetup_repo" "$user_archsetup_dir" && \ chown -R "$username": "/home/$username/code") \ >> "$logfile" 2>&1 || error_warn "$action" "$?" @@ -741,8 +797,9 @@ user_customizations() { pacman_install desktop-file-utils action="updating desktop database" && display "task" "$action" + # Exit code 1 means warnings (missing icons, etc.) but database still updated (sudo -u "$username" update-desktop-database "/home/$username/.local/share/applications" \ - >> "$logfile" 2>&1 ) || error_warn "$action" "$?" + >> "$logfile" 2>&1 ) || true action="restoring dotfile versions" && display "task" "$action" (cd "$dotfiles_dir" && git config --global --add safe.directory "$user_archsetup_dir" && \ @@ -828,8 +885,11 @@ ethernet.cloned-mac-address=stable EOF # Configure wireless regulatory domain (enables full WiFi capabilities for region) - action="configuring wireless regulatory domain (US)" && display "task" "$action" - sed -i 's/^#WIRELESS_REGDOM="US"/WIRELESS_REGDOM="US"/' /etc/conf.d/wireless-regdom + # Derive region code from locale (e.g., en_US.UTF-8 → US, de_DE.UTF-8 → DE) + current_lang="${LANG:-en_US.UTF-8}" + wireless_region="${current_lang:3:2}" # extract country code (positions 3-4) + action="configuring wireless regulatory domain ($wireless_region)" && display "task" "$action" + sed -i "s/^#WIRELESS_REGDOM=\"$wireless_region\"/WIRELESS_REGDOM=\"$wireless_region\"/" /etc/conf.d/wireless-regdom # Encrypted DNS (DNS over TLS) @@ -842,6 +902,8 @@ DNS=1.1.1.1#cloudflare-dns.com 9.9.9.9#dns.quad9.net FallbackDNS=1.0.0.1#cloudflare-dns.com 149.112.112.112#dns.quad9.net DNSOverTLS=yes DNSSEC=yes +# Disable mDNS in resolved - avahi handles .local resolution exclusively +MulticastDNS=no EOF # Configure NetworkManager to use systemd-resolved @@ -941,6 +1003,9 @@ EOF systemctl enable ufw.service >> "$logfile" 2>&1 || error_warn "$action" "$?" # Verify firewall is actually active + # Note: In VM environments, UFW may show inactive due to missing kernel + # netfilter modules. This is a test environment limitation, not a bug. + # On real hardware with proper kernel support, UFW activates correctly. action="verifying firewall is active" && display "task" "$action" if ! ufw status | grep -q "Status: active"; then error_messages=("FIREWALL NOT ACTIVE - run: sudo ufw enable" "${error_messages[@]}") @@ -1066,81 +1131,8 @@ EOF use_template = none EOF - action="creating zfs-replicate script" && display "task" "$action" - cat << 'EOF' > /usr/local/bin/zfs-replicate -#!/bin/bash -# zfs-replicate - Replicate ZFS datasets to TrueNAS -# -# Usage: -# zfs-replicate # Replicate all configured datasets -# zfs-replicate [dataset] # Replicate specific dataset - -set -e - -# TrueNAS Configuration -# Try local network first, fall back to tailscale -TRUENAS_LOCAL="truenas.local" -TRUENAS_TAILSCALE="truenas" -TRUENAS_USER="root" -TRUENAS_POOL="vault" -BACKUP_PATH="backups" # TODO: Configure actual path - -# Datasets to replicate -DATASETS="zroot/ROOT/default zroot/home zroot/media zroot/vms" - -# Colors -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' - -info() { echo -e "${GREEN}[INFO]${NC} $1"; } -warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } -error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; } - -command -v syncoid >/dev/null 2>&1 || error "syncoid not found. Install sanoid package." - -# Determine which host to use -determine_host() { - if ping -c 1 -W 2 "$TRUENAS_LOCAL" &>/dev/null; then - echo "$TRUENAS_LOCAL" - elif ping -c 1 -W 2 "$TRUENAS_TAILSCALE" &>/dev/null; then - echo "$TRUENAS_TAILSCALE" - else - error "Cannot reach TrueNAS at $TRUENAS_LOCAL or $TRUENAS_TAILSCALE" - fi -} - -TRUENAS_HOST=$(determine_host) -info "Using TrueNAS host: $TRUENAS_HOST" - -# Single dataset mode -if [[ -n "$1" ]]; then - dataset="$1" - dest="$TRUENAS_USER@$TRUENAS_HOST:$TRUENAS_POOL/$BACKUP_PATH/${dataset#zroot/}" - info "Replicating $dataset -> $dest" - syncoid --recursive "$dataset" "$dest" - exit 0 -fi - -# Full replication -info "Starting ZFS replication to $TRUENAS_HOST" -echo "" - -for dataset in $DATASETS; do - dest="$TRUENAS_USER@$TRUENAS_HOST:$TRUENAS_POOL/$BACKUP_PATH/${dataset#zroot/}" - info "Replicating $dataset -> $dest" - - if syncoid --recursive "$dataset" "$dest"; then - info " Success" - else - warn " Failed (will retry next run)" - fi - echo "" -done - -info "Replication complete." -EOF + action="installing zfs-replicate script" && display "task" "$action" + cp "$user_archsetup_dir/scripts/zfs-replicate" /usr/local/bin/zfs-replicate chmod +x /usr/local/bin/zfs-replicate action="creating zfs-replicate systemd service" && display "task" "$action" @@ -1246,7 +1238,7 @@ dwm() { action="DWM Window Manager Dependencies" && display "subtitle" "$action" for software in coreutils fontconfig freetype2 glibc harfbuzz libx11 libxft libxinerama; do pacman_install "$software" - done; + done action="DWM Window Manager" && display "subtitle" "$action" @@ -1350,7 +1342,7 @@ desktop_environment() { for software in alsa-utils pipewire wireplumber pipewire-pulse \ pipewire-docs pamixer pulsemixer ffmpeg rtkit; do pacman_install "$software" - done; + done # disable the pc speaker beep rmmod pcspkr 2>/dev/null || true echo "blacklist pcspkr" > /etc/modprobe.d/nobeep.conf @@ -1386,11 +1378,11 @@ desktop_environment() { for software in htop mc ncdu tmux fzf zip unzip atool wget detox \ lsof usbutils moreutils; do pacman_install "$software" - done; + done for software in task-spooler-cpu speedtest-go-bin gotop-bin rar; do aur_install "$software" - done; + done # Help And Documentation @@ -1422,7 +1414,7 @@ desktop_environment() { for software in brightnessctl network-manager-applet xclip rofi \ conky qalculate-gtk feh; do pacman_install "$software" - done; + done aur_install caffeine-ng aur_install flameshot @@ -1436,12 +1428,12 @@ desktop_environment() { for software in picom lxappearance gnome-themes-extra; do pacman_install "$software" - done; + done for software in vimix-cursors \ papirus-icon-theme qt6ct qt5ct; do aur_install "$software" - done; + done pacman_install libappindicator-gtk3 # required by some applets @@ -1834,18 +1826,9 @@ outro() { preflight_checks # verify system requirements (always runs) STARTTIME=$(date +%s) # must be outside intro() since it may be skipped on resume -run_step "intro" intro -run_step "prerequisites" prerequisites -run_step "create_user" create_user -run_step "user_customizations" user_customizations -run_step "aur_installer" aur_installer -run_step "essential_services" essential_services -run_step "xorg" xorg -run_step "dwm" dwm -run_step "desktop_environment" desktop_environment -run_step "developer_workstation" developer_workstation -run_step "supplemental_software" supplemental_software -run_step "boot_ux" boot_ux +for step in "${STEPS[@]}"; do + run_step "$step" "$step" +done outro # take end stats; show summary (always runs) diff --git a/archsetup.conf.example b/archsetup.conf.example index 4d03fe1..54db69e 100644 --- a/archsetup.conf.example +++ b/archsetup.conf.example @@ -31,6 +31,11 @@ PASSWORD=welcome # Set to "yes" if you want to handle GPU drivers manually #NO_GPU_DRIVERS=no +# System locale (default: prompt if not already configured) +# Common options: en_US.UTF-8, en_GB.UTF-8, de_DE.UTF-8, es_ES.UTF-8, +# fr_FR.UTF-8, pt_BR.UTF-8, ja_JP.UTF-8, zh_CN.UTF-8 +#LOCALE=en_US.UTF-8 + ############################# # Git Repositories ############################# @@ -42,3 +47,4 @@ PASSWORD=welcome #ST_REPO=https://github.com/yourusername/st.git #SLOCK_REPO=https://github.com/yourusername/slock.git #DOTEMACS_REPO=https://github.com/yourusername/dotemacs.git +#ARCHSETUP_REPO=https://github.com/yourusername/archsetup.git diff --git a/assets/2026-01-23-avahi-mdns-fixes.org b/assets/2026-01-23-avahi-mdns-fixes.org new file mode 100644 index 0000000..89b005e --- /dev/null +++ b/assets/2026-01-23-avahi-mdns-fixes.org @@ -0,0 +1,125 @@ +#+TITLE: Avahi/mDNS Configuration Fixes +#+DATE: 2026-01-23 + +* Problem Summary + +On velox, mDNS hostname resolution was not working correctly from other machines on the LAN (e.g., ratio). Attempting to access =http://velox.local:8384= (Syncthing web UI) failed, while accessing via IP address worked. + +* Issues Identified + +** Issue 1: Hostname Conflict (velox-3.local) + +*Symptom:* Avahi was running as =velox-3.local= instead of =velox.local= + +*Cause:* Avahi was publishing on multiple network interfaces including virtual ones: +- =enp0s13f0u3= (physical LAN - correct) +- =docker0= (Docker bridge) +- =virbr0= (libvirt bridge) +- =vnet0= (VM virtual NIC) +- =tailscale0= (Tailscale VPN) + +Each interface was effectively registering as a separate host, causing mDNS hostname conflicts with itself. + +*Solution:* Restrict Avahi to only the physical LAN interface. + +#+begin_src conf +# /etc/avahi/avahi-daemon.conf +[server] +allow-interfaces=enp0s13f0u3 +#+end_src + +** Issue 2: IPv6-Only Resolution + +*Symptom:* =velox.local= resolved to IPv6 link-local address (=fe80::...=) only, but Syncthing was listening on IPv4 only (=0.0.0.0:8384=). + +*Cause:* Default Avahi configuration does not publish A records (IPv4) in response to AAAA queries (IPv6). + +*Solution:* Enable =publish-a-on-ipv6= to ensure IPv4 addresses are returned. + +#+begin_src conf +# /etc/avahi/avahi-daemon.conf +[publish] +publish-a-on-ipv6=yes +#+end_src + +** Issue 3: Conflicting mDNS Stacks + +*Symptom:* Avahi logged warning: "Detected another IPv4 mDNS stack running on this host" + +*Cause:* Both =avahi-daemon= and =systemd-resolved= were configured to handle mDNS: + +#+begin_src conf +# /etc/systemd/resolved.conf (before fix) +[Resolve] +MulticastDNS=yes +#+end_src + +*Solution:* Disable mDNS in systemd-resolved, let Avahi handle it exclusively. + +#+begin_src conf +# /etc/systemd/resolved.conf +[Resolve] +Domains=~local +MulticastDNS=no +#+end_src + +* Complete Fix Applied + +** Files Modified + +*** /etc/avahi/avahi-daemon.conf + +Changes made: +#+begin_src diff +-#allow-interfaces=eth0 ++allow-interfaces=enp0s13f0u3 + +-#publish-a-on-ipv6=no ++publish-a-on-ipv6=yes +#+end_src + +*** /etc/systemd/resolved.conf + +Changes made: +#+begin_src diff +-MulticastDNS=yes ++MulticastDNS=no +#+end_src + +** Services Restarted + +#+begin_src bash +sudo systemctl restart systemd-resolved +sudo systemctl restart avahi-daemon +#+end_src + +* Verification + +After fixes: +- Avahi runs as =velox.local= (not =velox-3.local=) +- No mDNS stack conflict warning +- From ratio: =avahi-resolve -n velox.local= returns =192.168.86.42= +- From ratio: =curl http://velox.local:8384/= returns HTTP 200 + +* Notes for archsetup + +These configurations should be added to the Arch setup scripts: + +1. Install avahi: =pacman -S avahi nss-mdns= + +2. Configure =/etc/avahi/avahi-daemon.conf=: + - Set =allow-interfaces= to physical LAN interface (determine dynamically or prompt user) + - Set =publish-a-on-ipv6=yes= + +3. Configure =/etc/systemd/resolved.conf=: + - Set =MulticastDNS=no= to avoid conflict with Avahi + +4. Enable and start avahi-daemon: + #+begin_src bash + systemctl enable --now avahi-daemon + #+end_src + +5. Ensure =/etc/nsswitch.conf= has mdns in hosts line: + #+begin_src conf + hosts: mymachines mdns_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] files dns + #+end_src diff --git a/scripts/zfs-replicate b/scripts/zfs-replicate new file mode 100755 index 0000000..cf946f1 --- /dev/null +++ b/scripts/zfs-replicate @@ -0,0 +1,72 @@ +#!/bin/bash +# zfs-replicate - Replicate ZFS datasets to TrueNAS +# +# Usage: +# zfs-replicate # Replicate all configured datasets +# zfs-replicate [dataset] # Replicate specific dataset + +set -e + +# TrueNAS Configuration +# Try local network first, fall back to tailscale +TRUENAS_LOCAL="truenas.local" +TRUENAS_TAILSCALE="truenas" +TRUENAS_USER="root" +TRUENAS_POOL="vault" +BACKUP_PATH="backups" # TODO: Configure actual path + +# Datasets to replicate +DATASETS="zroot/ROOT/default zroot/home zroot/media zroot/vms" + +# Colors +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $1"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; } + +command -v syncoid >/dev/null 2>&1 || error "syncoid not found. Install sanoid package." + +# Determine which host to use +determine_host() { + if ping -c 1 -W 2 "$TRUENAS_LOCAL" &>/dev/null; then + echo "$TRUENAS_LOCAL" + elif ping -c 1 -W 2 "$TRUENAS_TAILSCALE" &>/dev/null; then + echo "$TRUENAS_TAILSCALE" + else + error "Cannot reach TrueNAS at $TRUENAS_LOCAL or $TRUENAS_TAILSCALE" + fi +} + +TRUENAS_HOST=$(determine_host) +info "Using TrueNAS host: $TRUENAS_HOST" + +# Single dataset mode +if [[ -n "$1" ]]; then + dataset="$1" + dest="$TRUENAS_USER@$TRUENAS_HOST:$TRUENAS_POOL/$BACKUP_PATH/${dataset#zroot/}" + info "Replicating $dataset -> $dest" + syncoid --recursive "$dataset" "$dest" + exit 0 +fi + +# Full replication +info "Starting ZFS replication to $TRUENAS_HOST" +echo "" + +for dataset in $DATASETS; do + dest="$TRUENAS_USER@$TRUENAS_HOST:$TRUENAS_POOL/$BACKUP_PATH/${dataset#zroot/}" + info "Replicating $dataset -> $dest" + + if syncoid --recursive "$dataset" "$dest"; then + info " Success" + else + warn " Failed (will retry next run)" + fi + echo "" +done + +info "Replication complete." @@ -655,6 +655,61 @@ Steps: Reference: Removed from archsetup on 2025-11-15 +** Method 6: Add Wayland/Hyprland Desktop Alternative + +Currently archsetup installs only Xorg with DWM. This method adds a Wayland-based alternative using Hyprland that preserves the same workflow and keybindings while modernizing the graphics stack. + +*Goal:* Post-install, user can choose between: +- Xorg + DWM + dmenu + st + sxhkd (current) +- Wayland + Hyprland + rofi + foot + polybar (new) + +*Why this matters:* +- Wayland is the future; Xorg maintenance is declining +- Hyprland offers smooth animations and modern compositor features +- Polybar provides more pleasant status bar aesthetics +- Same keybindings = same muscle memory, no relearning +- Choice allows gradual migration without losing fallback + +*** TODO [#A] Create Wayland/Hyprland desktop environment option +**** Requirements +- Same keybindings as DWM/sxhkd (translate config) +- Visual style matches current DWM setup initially +- Components: hyprland, rofi-wayland, foot, polybar +- Dotfiles: hyprland.conf, rofi config, foot.ini, polybar config + +**** Implementation considerations +- Keybinding translation: sxhkdrc → hyprland.conf format +- DWM window rules → Hyprland windowrules +- Polybar needs Hyprland IPC module for workspaces +- rofi-wayland vs rofi (X11) - may need separate configs +- foot terminal vs st - similar minimalist philosophy +- Screen locking: swaylock instead of slock +- Wallpaper: hyprpaper or swaybg instead of feh +- Screenshots: grim+slurp instead of maim + +**** User interface for selection +Options to explore: +- Config file option: DESKTOP=xorg or DESKTOP=wayland +- Interactive prompt during install +- Install both, choose at login (display manager or .xinitrc equivalent) +- Separate scripts: archsetup-xorg, archsetup-wayland + +**** Phased approach +1. Create working Hyprland config with matching keybindings +2. Test on current system manually +3. Add to dotfiles/ +4. Add archsetup logic to install Wayland stack +5. Implement user selection mechanism +6. Document differences and gotchas + +*** TODO [#B] Write detailed Hyprland implementation plan +Create docs/hyprland-implementation-plan.org with: +- Full keybinding mapping from sxhkdrc +- Package list comparison (Xorg vs Wayland) +- Config file locations and formats +- Testing checklist +- Rollback strategy + * Obstacles ** Limited Security Knowledge |
