summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-01-17 17:55:20 -0600
committerCraig Jennings <c@cjennings.net>2026-01-17 17:55:20 -0600
commite13933a47ed9814927f46d24ef58c969f2e4d0ac (patch)
treeb85ebbad730a5c2cf978cc73a64df46e58d0f280
parent91c8e32100b1062529451cc5466aca669f31fd6c (diff)
feat(archsetup): ZFS/sanoid support, gvfs-smb, bug fixes
- Add ZFS detection with sanoid/syncoid for snapshot management - Add gvfs-smb for Thunar SMB network browsing - Fix shell quoting throughout script - Fix stale $action variables in error handlers - Fix display() return values (was returning 1) - Fix mkinitcpio.conf sed pattern to be flexible - Fix vconsole.conf duplicate entries on re-run - Fix systemd unit overrides using drop-in files - Fix ufw port typo (55353 -> 5353) - Fix GRUB_RECORDFAIL_TIMEOUT undefined variable - Enable NetworkManager service - Move thunar, libvips, isync to pacman (now in official repos) - Clean up reflector config with heredoc - Remove unnecessary sudo when already root - Convert shebang from sh to bash
-rwxr-xr-xarchsetup350
-rw-r--r--assets/2026-01-17-gvfs-smb-feature-request.txt6
-rw-r--r--assets/2026-01-17-zfs-sanoid-feature-request.txt202
3 files changed, 488 insertions, 70 deletions
diff --git a/archsetup b/archsetup
index c3d1d69..66a4b87 100755
--- a/archsetup
+++ b/archsetup
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
# ArchSetup - Craig Jennings <craigmartinjennings@gmail.com>
# License: GNU GPLv3
@@ -250,14 +250,15 @@ display () {
case "$1" in
"title")
printf "\n##### %s\n" "$2" | tee -a "$logfile"
- return 1;
+ return 0;
;;
"subtitle")
printf "\n%s\n" "$2" | tee -a "$logfile"
+ return 0;
;;
"task")
printf "...%s @ %s\n" "$2" "$(date +'%T')" | tee -a "$logfile"
- return 1;
+ return 0;
;;
*)
printf "CRASH: display () called with incorrect arguments.\n"
@@ -322,6 +323,12 @@ pip_install() {
error "error" "$action" "$?"
}
+# Filesystem Detection
+is_zfs_root() {
+ # Returns 0 (true) if root filesystem is ZFS, 1 (false) otherwise
+ [ "$(findmnt -n -o FSTYPE /)" = "zfs" ]
+}
+
### Prerequisites
prerequisites() {
# why these software packages are 'required'
@@ -383,18 +390,17 @@ prerequisites() {
pacman_install reflector
action="configuring reflector" && display "task" "$action"
- (printf '
- --connection-timeout 3 \
- --download-timeout 3 \
- --protocol https \
- --age 12 \
- --latest 20 \
- --score 10 \
- --fastest 5 \
- --sort score \
- --save /etc/pacman.d/mirrorlist
- ' > /etc/xdg/reflector/reflector.conf >> "$logfile" 2>&1) || \
- error "error" "$action" "$?"
+ cat << 'EOF' > /etc/xdg/reflector/reflector.conf
+--connection-timeout 3
+--download-timeout 3
+--protocol https
+--age 12
+--latest 20
+--score 10
+--fastest 5
+--sort score
+--save /etc/pacman.d/mirrorlist
+EOF
# Skip running reflector during installation - it can cause SSH disconnections during testing
# The reflector.timer will update mirrors automatically later
@@ -417,7 +423,7 @@ prerequisites() {
[ -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 "crash" "creating the directory $source_dir"
+ (mkdir -p "$source_dir") || error "crash" "creating the directory $source_dir"
}
@@ -453,7 +459,7 @@ create_user () {
|| error "error" "$action" "$?"
# mount as ramdisk to speed aur/git build/installs
- (sudo mount -t tmpfs -o size=4G archsetup $source_dir >> "$logfile" 2>&1) || \
+ (mount -t tmpfs -o size=4G archsetup "$source_dir" >> "$logfile" 2>&1) || \
error "crash" "mounting the RAM disk for archsetup" "$?"
(chown -R "$username":wheel "$(dirname "$source_dir")" >> "$logfile" 2>&1) || \
@@ -478,7 +484,7 @@ user_customizations() {
dotfiles_dir="$user_archsetup_dir/dotfiles"
action="linking dotfiles into place" && display "task" "$action"
- (cd $dotfiles_dir && stow --target=/home/$username --no-folding --adopt system \
+ (cd "$dotfiles_dir" && stow --target="/home/$username" --no-folding --adopt system \
>> "$logfile" 2>&1 ) || error "error" "$action" "$?"
action="restoring dotfile versions" && display "task" "$action"
@@ -538,18 +544,23 @@ essential_services() {
display "subtitle" "Randomness"
pacman_install rng-tools
+ action="enabling rngd service" && display "task" "$action"
systemctl enable rngd >> "$logfile" 2>&1 || error "error" "$action" "$?"
+ action="starting rngd service" && display "task" "$action"
systemctl start rngd >> "$logfile" 2>&1 || error "error" "$action" "$?"
# Networking
display "subtitle" "Networking"
pacman_install networkmanager
+ action="enabling NetworkManager" && display "task" "$action"
+ systemctl enable NetworkManager.service >> "$logfile" 2>&1 || error "error" "$action" "$?"
# Power
display "subtitle" "Power"
pacman_install upower
+ action="enabling upower service" && display "task" "$action"
systemctl enable upower >> "$logfile" 2>&1 || error "error" "$action" "$?"
# Secure Shell
@@ -586,7 +597,7 @@ essential_services() {
"80,443,8080/tcp" \
"9040,9050,9051,9053,9119/tcp" \
"IMAP" "IMAPS" \
- "55353/udp" \
+ "5353/udp" \
"ssh" \
"22000/tcp" "22000/udp" "21027/udp" \
"42001/tcp" "42001/udp" \
@@ -595,13 +606,13 @@ essential_services() {
"transmission" \
; do
action="adding ufw rule to allow $protocol" && display "task" "$action"
- (ufw allow $protocol >> "$logfile" 2>&1) || error "error" "$action" "$?"
+ (ufw allow "$protocol" >> "$logfile" 2>&1) || error "error" "$action" "$?"
done
action="adding limits to protect from brute force attacks" && display "task" "$action"
(ufw limit 22/tcp >> "$logfile" 2>&1 && \
ufw limit 443/tcp >> "$logfile" 2>&1) || \
- error "error" "action"
+ error "error" "$action" "$?"
action="enabling firewall service to launch on boot" && display "task" "$action"
systemctl enable ufw.service >> "$logfile" 2>&1 || error "error" "$action" "$?"
@@ -640,25 +651,213 @@ essential_services() {
action="enabling the package cache cleanup timer" && display "task" "$action"
systemctl enable --now paccache.timer >> "$logfile" 2>&1 || error "error" "$action" "$?"
- # Timeshift-Autosnap
+ # Snapshot Service - filesystem-aware
display "subtitle" "Snapshot Service"
- aur_install timeshift-autosnap
- pacman_install grub-btrfs
- action="enabling snapshot boot menu updates" && display "task" "$action"
- systemctl enable grub-btrfsd >> "$logfile" 2>&1 || error "error" "$action" "$?"
-
- action="starting snapshot boot menu updates" && display "task" "$action"
- # starting and stopping service to generate the grub-btrfs config
- systemctl start grub-btrfsd >> "$logfile" 2>&1 || error "error" "$action" "$?"
- systemctl stop grub-btrfsd >> "$logfile" 2>&1 || error "error" "$action" "$?"
- # edit grub-btrfs config for timeshift auto snapshot support
- sed -i \
- 's|ExecStart=/usr/bin/grub-btrfsd --syslog /.snapshots|ExecStart=/usr/bin/grub-btrfsd --syslog --timeshift-auto|' \
- /etc/systemd/system/grub-btrfsd.service
-
-
- action="regenerating boot menu" && display "task" "$action"
- grub-mkconfig -o /boot/grub/grub.cfg >> "$logfile" 2>&1 || error "error" "$action" "$?"
+
+ if is_zfs_root; then
+ # ZFS: Install sanoid for snapshot management
+ display "task" "ZFS detected - installing sanoid"
+ aur_install sanoid
+
+ action="creating sanoid configuration" && display "task" "$action"
+ mkdir -p /etc/sanoid
+ cat << 'EOF' > /etc/sanoid/sanoid.conf
+# Sanoid configuration for ZFS snapshots
+# Less aggressive - TrueNAS handles long-term backups
+
+#############################
+# Templates
+#############################
+
+[template_production]
+ # Local rollback capability
+ hourly = 6
+ daily = 7
+ weekly = 2
+ monthly = 1
+ autosnap = yes
+ autoprune = yes
+
+[template_backup]
+ # Less frequent for large/static data
+ hourly = 0
+ daily = 3
+ weekly = 2
+ monthly = 1
+ autosnap = yes
+ autoprune = yes
+
+[template_none]
+ autosnap = no
+ autoprune = yes
+
+#############################
+# Datasets
+#############################
+
+[zroot/ROOT/default]
+ use_template = production
+
+[zroot/home]
+ use_template = production
+ recursive = yes
+
+[zroot/media]
+ use_template = backup
+
+[zroot/vms]
+ use_template = backup
+
+[zroot/var/log]
+ use_template = production
+
+[zroot/var/lib/pacman]
+ use_template = production
+
+[zroot/var/cache]
+ use_template = none
+
+[zroot/var/tmp]
+ use_template = none
+
+[zroot/tmp]
+ 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
+ chmod +x /usr/local/bin/zfs-replicate
+
+ action="creating zfs-replicate systemd service" && display "task" "$action"
+ cat << 'EOF' > /etc/systemd/system/zfs-replicate.service
+[Unit]
+Description=ZFS Replication to TrueNAS
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/usr/local/bin/zfs-replicate
+User=root
+
+[Install]
+WantedBy=multi-user.target
+EOF
+
+ action="creating zfs-replicate systemd timer" && display "task" "$action"
+ cat << 'EOF' > /etc/systemd/system/zfs-replicate.timer
+[Unit]
+Description=Run ZFS replication nightly
+
+[Timer]
+OnCalendar=*-*-* 02:00:00
+RandomizedDelaySec=1800
+Persistent=true
+
+[Install]
+WantedBy=timers.target
+EOF
+
+ action="enabling sanoid timer" && display "task" "$action"
+ systemctl enable sanoid.timer >> "$logfile" 2>&1 || error "error" "$action" "$?"
+
+ # Note: zfs-replicate.timer is NOT enabled automatically
+ # User must set up SSH key auth to TrueNAS first, then run:
+ # systemctl enable --now zfs-replicate.timer
+ display "task" "zfs-replicate timer created (enable after SSH key setup to TrueNAS)"
+
+ else
+ # Non-ZFS (btrfs): Install timeshift-autosnap
+ aur_install timeshift-autosnap
+ pacman_install grub-btrfs
+ action="enabling snapshot boot menu updates" && display "task" "$action"
+ systemctl enable grub-btrfsd >> "$logfile" 2>&1 || error "error" "$action" "$?"
+
+ action="starting snapshot boot menu updates" && display "task" "$action"
+ # starting and stopping service to generate the grub-btrfs config
+ systemctl start grub-btrfsd >> "$logfile" 2>&1 || error "error" "$action" "$?"
+ systemctl stop grub-btrfsd >> "$logfile" 2>&1 || error "error" "$action" "$?"
+ # edit grub-btrfs config for timeshift auto snapshot support
+ sed -i \
+ 's|ExecStart=/usr/bin/grub-btrfsd --syslog /.snapshots|ExecStart=/usr/bin/grub-btrfsd --syslog --timeshift-auto|' \
+ /etc/systemd/system/grub-btrfsd.service
+
+ action="regenerating boot menu" && display "task" "$action"
+ grub-mkconfig -o /boot/grub/grub.cfg >> "$logfile" 2>&1 || error "error" "$action" "$?"
+ fi
}
### Xorg Display Manager
@@ -673,7 +872,7 @@ xorg() {
xorg-xdpyinfo xorg-xprop \
xorg-xwininfo xorg-xhost \
xorg-xinput xorg-xkill ; do
- pacman_install $software
+ pacman_install "$software"
done
# disallow vt switching or zapping the xorg server to bypass screen lock
@@ -692,15 +891,15 @@ dwm() {
action="DWM Window Manager Dependencies" && display "subtitle" "$action"
for software in coreutils fontconfig freetype2 glibc libx11 libxft libxinerama; do
- pacman_install $software
+ pacman_install "$software"
done;
action="DWM Window Manager" && display "subtitle" "$action"
- git_install $dwm_repo
- git_install $dmenu_repo
- git_install $st_repo
- git_install $slock_repo
+ git_install "$dwm_repo"
+ git_install "$dmenu_repo"
+ git_install "$st_repo"
+ git_install "$slock_repo"
aur_install pinentry-dmenu # password entry that leverages dmenu
}
@@ -776,11 +975,11 @@ desktop_environment() {
action="Audio System" && display "subtitle" "$action"
for software in alsa-utils pipewire wireplumber pipewire-pulse \
pipewire-docs pamixer pulsemixer ffmpeg; do
- pacman_install $software
+ pacman_install "$software"
done;
# disable the pc speaker beep
rmmod pcspkr >> "$logfile" 2>&1
- echo "blacklist pcspkr" >/etc/modprobe.d/nobeep.conf >> "$logfile" 2>&1
+ echo "blacklist pcspkr" > /etc/modprobe.d/nobeep.conf
# Keyboard Shortcut Manager
@@ -802,7 +1001,7 @@ desktop_environment() {
action="Bluetooth System" && display "subtitle" "$action"
for software in bluez bluez-utils blueman; do
- pacman_install $software
+ pacman_install "$software"
done
action="enabling bluetooth to launch at boot" && display "task" "$action"
systemctl enable bluetooth.service >> "$logfile" 2>&1 || error "error" "$action" "$?"
@@ -832,7 +1031,7 @@ desktop_environment() {
action="Sync Services" && display "subtitle" "$action"
pacman_install syncthing
- systemctl enable syncthing@$username.service >> "$logfile" 2>&1 || error "error" "$action" "$?"
+ systemctl enable "syncthing@$username.service" >> "$logfile" 2>&1 || error "error" "$action" "$?"
# Desktop Environment Utilities
@@ -840,7 +1039,7 @@ desktop_environment() {
for software in brightnessctl network-manager-applet xclip rofi \
conky qalculate-gtk feh; do
- pacman_install $software
+ pacman_install "$software"
done;
aur_install caffeine-ng
@@ -854,12 +1053,12 @@ desktop_environment() {
action="UI Theme" && display "subtitle" "$action"
for software in picom lxappearance gnome-themes-extra; do
- pacman_install $software
+ pacman_install "$software"
done;
for software in vimix-cursors \
papirus-icon-theme qt6ct qt5ct; do
- aur_install $software
+ aur_install "$software"
done;
pacman_install libappindicator-gtk3 # required by some applets
@@ -953,8 +1152,8 @@ developer_workstation () {
# supporting utilities used by my emacs configuration
aur_install exercism-bin # command line tool for exercism.io
- aur_install libvips # image previewer library
- aur_install isync # email sync
+ pacman_install libvips # image previewer library
+ pacman_install isync # email sync
aur_install epub-thumbnailer-git # epub previews in dirvish
aur_install mu # email indexer and utilities
aur_install multimarkdown # markdown conversion
@@ -1004,7 +1203,7 @@ developer_workstation () {
pacman_install docker
pacman_install docker-compose
action="adding user to docker group" && display "task" "$action"
- (gpasswd -a $username docker >> "$logfile" 2>&1) || error "error" "$action" "$?"
+ (gpasswd -a "$username" docker >> "$logfile" 2>&1) || error "error" "$action" "$?"
action="enabling docker service to launch on boot" && display "task" "$action"
systemctl enable docker.service >> "$logfile" 2>&1 || error "error" "$action" "$?"
}
@@ -1091,7 +1290,8 @@ supplemental_software() {
aur_install nsxiv # image viewer
aur_install snore-git # sleep with feedback
# aur_install tageditor # metadata editor for mkv, webm and related video files (DISABLED: hangs indefinitely building qt5-webengine dependency)
- aur_install thunar # file manager
+ pacman_install thunar # file manager
+ pacman_install gvfs-smb # SMB network share browsing in Thunar
if ! $skip_slow_packages; then
aur_install topgrade # upgrade everything utility (SLOW: ~12 min, Rust compilation)
fi
@@ -1118,20 +1318,30 @@ boot_ux() {
error "error" "$action" "$?"
action="configuring console font" && display "task" "$action"
- (echo "FONT=ter-132n" >>/etc/vconsole.conf) || error "error" "$action" "$?"
+ if grep -q "^FONT=" /etc/vconsole.conf 2>/dev/null; then
+ sed -i 's/^FONT=.*/FONT=ter-132n/' /etc/vconsole.conf
+ else
+ echo "FONT=ter-132n" >> /etc/vconsole.conf
+ fi
action="delegating fsck messages from udev to systemd" && display "task" "$action"
- sed -i "s/.*HOOKS=(base udev autodetect keyboard keymap modconf block filesystems fsck).*/HOOKS=(base systemd autodetect keyboard keymap modconf block filesystems fsck)/" /etc/mkinitcpio.conf || error "error" "running sed on mkinitcpio.conf to hide fsck messages" "$?"
+ sed -i '/^HOOKS=/ s/\budev\b/systemd/' /etc/mkinitcpio.conf || error "error" "$action" "$?"
mkinitcpio -P >> "$logfile" 2>&1 || error "error" "running mkinitcpio -P to silence fsck messages" "$?"
- action="instructing systemd to check filesystems" && display "task" "$action"
- servicefile=/usr/lib/systemd/system/systemd-fsck-root.service
- [ -f $servicefile ] && echo "StandardOutput=null" >>$servicefile && \
- echo "StandardError=journal+console" >>$servicefile
+ action="configuring quiet fsck output" && display "task" "$action"
+ mkdir -p /etc/systemd/system/systemd-fsck-root.service.d
+ cat << 'EOF' > /etc/systemd/system/systemd-fsck-root.service.d/quiet.conf
+[Service]
+StandardOutput=null
+StandardError=journal+console
+EOF
- servicefile=/usr/lib/systemd/system/systemd-fsck@.service
- [ -f $servicefile ] && echo "StandardOutput=null" >>$servicefile && \
- echo "StandardError=journal+console" >>$servicefile
+ mkdir -p /etc/systemd/system/systemd-fsck@.service.d
+ cat << 'EOF' > /etc/systemd/system/systemd-fsck@.service.d/quiet.conf
+[Service]
+StandardOutput=null
+StandardError=journal+console
+EOF
# action="removing hostname from login prompt" && display "task" "$action"
# sed -i "s/--noclear/--nohostname --noclear/g" /usr/lib/systemd/system/getty@.service \
@@ -1152,9 +1362,9 @@ boot_ux() {
sed -i "s/.*GRUB_DEFAULT=.*/GRUB_DEFAULT=0/g" /etc/default/grub
sed -i 's/.*GRUB_TERMINAL_OUTPUT=console/GRUB_TERMINAL_OUTPUT=gfxterm/' /etc/default/grub
sed -i 's/.*GRUB_GFXMODE=auto/GRUB_GFXMODE=1024x768/' /etc/default/grub
- sed -i "s/.*GRUB_RECORDFAIL_TIMEOUT=.*/GRUB_RECORDFAIL_TIMEOUT=$GRUB_TIMEOUT/g" /etc/default/grub
+ sed -i "s/.*GRUB_RECORDFAIL_TIMEOUT=.*/GRUB_RECORDFAIL_TIMEOUT=2/g" /etc/default/grub
sed -i "s/.*GRUB_CMDLINE_LINUX_DEFAULT=.*/GRUB_CMDLINE_LINUX_DEFAULT=\"rw loglevel=2 rd.systemd.show_status=auto rd.udev.log_level=2 nvme.noacpi=1 mem_sleep_default=deep nowatchdog quiet splash\"/g" /etc/default/grub
- grub-mkconfig -o /boot/grub/grub.cfg >> "$logfile" 2>&1 || error "error" "" "$?"
+ grub-mkconfig -o /boot/grub/grub.cfg >> "$logfile" 2>&1 || error "error" "generating grub config" "$?"
fi
}
@@ -1178,12 +1388,12 @@ outro() {
mins=$(($totalsecs / 60))
secs=$(($totalsecs % 60))
- new_packages=$(cat $archsetup_packages | wc -l)
+ new_packages=$(wc -l < "$archsetup_packages")
printf "\n"
printf "Completion time : %s\n" "$(date +'%D %T')" | tee -a "$logfile"
- printf "Elapsed time : %s minutes, %s seconds\n" $mins $secs | tee -a "$logfile"
- printf "Errors encountered : %s\n" $errors_encountered | tee -a "$logfile"
+ printf "Elapsed time : %s minutes, %s seconds\n" "$mins" "$secs" | tee -a "$logfile"
+ printf "Errors encountered : %s\n" "$errors_encountered" | tee -a "$logfile"
printf "Log file location : %s\n" "$logfile"
printf "Packages installed : %s\n" "$new_packages"
printf "\n"
diff --git a/assets/2026-01-17-gvfs-smb-feature-request.txt b/assets/2026-01-17-gvfs-smb-feature-request.txt
new file mode 100644
index 0000000..79892f7
--- /dev/null
+++ b/assets/2026-01-17-gvfs-smb-feature-request.txt
@@ -0,0 +1,6 @@
+Install gvfs-smb for Thunar SMB network browsing
+
+Package: gvfs-smb
+Install: sudo pacman -S gvfs-smb
+
+Without this package, Thunar cannot browse SMB/CIFS network shares.
diff --git a/assets/2026-01-17-zfs-sanoid-feature-request.txt b/assets/2026-01-17-zfs-sanoid-feature-request.txt
new file mode 100644
index 0000000..87207f2
--- /dev/null
+++ b/assets/2026-01-17-zfs-sanoid-feature-request.txt
@@ -0,0 +1,202 @@
+ZFS Detection and Sanoid Installation
+======================================
+
+When archsetup runs, it should detect if the system is on ZFS and install sanoid.
+
+Detection:
+- Check if root filesystem is ZFS: `findmnt -n -o FSTYPE /` returns "zfs"
+- Or check if zpool exists: `zpool list -H 2>/dev/null`
+
+If ZFS detected:
+1. Install sanoid from AUR: `yay -S sanoid`
+2. Create /etc/sanoid/sanoid.conf (see below)
+3. Enable the timer: `systemctl enable --now sanoid.timer`
+4. Create the syncoid replication script and systemd units (see below)
+
+Context:
+- install-archzfs can't install sanoid (AUR package)
+- archsetup already has AUR helper setup, so it's the right place to install it
+- syncoid (for TrueNAS replication) comes with the sanoid package
+
+Added: 2026-01-17
+
+================================================================================
+SANOID CONFIGURATION (/etc/sanoid/sanoid.conf)
+================================================================================
+
+# Sanoid configuration for ZFS snapshots
+# Less aggressive - TrueNAS handles long-term backups
+
+#############################
+# Templates
+#############################
+
+[template_production]
+ # Local rollback capability
+ hourly = 6
+ daily = 7
+ weekly = 2
+ monthly = 1
+ autosnap = yes
+ autoprune = yes
+
+[template_backup]
+ # Less frequent for large/static data
+ hourly = 0
+ daily = 3
+ weekly = 2
+ monthly = 1
+ autosnap = yes
+ autoprune = yes
+
+[template_none]
+ autosnap = no
+ autoprune = yes
+
+#############################
+# Datasets
+#############################
+
+[zroot/ROOT/default]
+ use_template = production
+
+[zroot/home]
+ use_template = production
+ recursive = yes
+
+[zroot/media]
+ use_template = backup
+
+[zroot/vms]
+ use_template = backup
+
+[zroot/var/log]
+ use_template = production
+
+[zroot/var/lib/pacman]
+ use_template = production
+
+[zroot/var/cache]
+ use_template = none
+
+[zroot/var/tmp]
+ use_template = none
+
+[zroot/tmp]
+ use_template = none
+
+================================================================================
+SYNCOID REPLICATION SCRIPT (/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."
+
+================================================================================
+SYSTEMD SERVICE (/etc/systemd/system/zfs-replicate.service)
+================================================================================
+
+[Unit]
+Description=ZFS Replication to TrueNAS
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/usr/local/bin/zfs-replicate
+User=root
+
+[Install]
+WantedBy=multi-user.target
+
+================================================================================
+SYSTEMD TIMER (/etc/systemd/system/zfs-replicate.timer)
+================================================================================
+
+[Unit]
+Description=Run ZFS replication nightly
+
+[Timer]
+OnCalendar=*-*-* 02:00:00
+RandomizedDelaySec=1800
+Persistent=true
+
+[Install]
+WantedBy=timers.target
+
+================================================================================
+ENABLE REPLICATION
+================================================================================
+
+After SSH key auth is set up to TrueNAS:
+ systemctl enable --now zfs-replicate.timer