Resume Automation & KeePassXC Sync
Overview
This system automates post-resume maintenance tasks, including Bluetooth reconnection, hardware daemon synchronization (tccd), and a conditional, rolling KeePassXC database backup. Architecture
To resolve udisksctl authentication failures and ensure proper session environment inheritance, the implementation uses a decoupled "Trigger/Worker" architecture.
-
Trigger (System Level): A root-owned service monitors system sleep targets. Upon wake, it initiates the user-level worker.
-
Worker (User Level): A user-owned service executes the script with full access to the display session, D-Bus, and user permissions.
Implementation Details
- System Trigger (/etc/systemd/system/resume-fix.service)
This service acts as the kernel-level listener. It does not run the script directly but initiates the user service.
[Unit]
Description=Trigger User Resume Scripts
After=suspend.target hibernate.target hybrid-sleep.target
[Service]
Type=oneshot
ExecStart=/usr/bin/su - simon -c "XDG_RUNTIME_DIR=/run/user/1000 systemctl --user start resume-worker.service"
[Install]
WantedBy=suspend.target hibernate.target hybrid-sleep.target
- User Worker (~/.config/systemd/user/resume-worker.service)
This service runs the script contextually within the active user session.
[Unit]
Description=User Resume Worker
After=graphical-session.target
[Service]
Type=oneshot
ExecStart=/home/simon/bin/resume.sh
[Install]
WantedBy=default.target
The file (/home/simon/bin/resume.sh) contains:
#!/usr/bin/fish
# --- 1. Delay & Environment Setup ---
sleep 5
set TARGET_USER "simon"
set DEVICE "4E:E7:34:D0:AA:6A"
set DISK "HPV222W"
set MOUNTS "/run/media/$TARGET_USER"
set KEEPASSFILE "/home/$TARGET_USER/Documents/Computer/SimonPasswords.kdbx"
# --- 2. Bluetooth Management ---
set STATE (rfkill | string match -r '.*bluetooth.*\\bblocked\\b')
if test -n "$STATE"
set ADAPTOR (rfkill | string match -r '.*bluetooth.*\\bblocked\\b' | awk '{print $1}')
rfkill unblock $ADAPTOR
sleep 2
end
expect -c "
set timeout 60
spawn bluetoothctl
send -- \"connect $DEVICE\r\"
expect \"Connection successful\"
send -- \"exit\r\"
expect eof
"
# --- 3. Hardware Daemon Sync ---
# Running directly as root from the service, this functions perfectly
sleep 2
if systemctl is-active --quiet tccd
systemctl restart tccd
end
# --- 4. KeePassXC Once-Per-Day Rolling Backup ---
if test -e /dev/disk/by-label/$DISK
# Mount using the target user session to avoid root ownership conflicts
if not test -d "$MOUNTS/$DISK"
udisksctl mount -b /dev/disk/by-label/$DISK
end
if test -d "$MOUNTS/$DISK"
set TODAY (date +%Y%m%d)
# Check if a backup matching today's date stamp already exists
set TODAY_CHECK (ls -1 "$MOUNTS/$DISK/" | string match -r "SimonPasswords_$TODAY\_\\d{6}\\.kdbx")
if test -z "$TODAY_CHECK"
# No backup found for today, proceed with backup
set TIMESTAMP (date +%Y%m%d_%H%M%S)
set BACKUP_NAME "SimonPasswords_$TIMESTAMP.kdbx"
cp "$KEEPASSFILE" "$MOUNTS/$DISK/$BACKUP_NAME"
chown $TARGET_USER:$TARGET_USER "$MOUNTS/$DISK/$BACKUP_NAME"
# Manage rolling backups: keep only 5 total matching the pattern
set ALL_BACKUPS (ls -1 "$MOUNTS/$DISK/" | string match -r '^SimonPasswords_\\d{8}_\\d{6}\\.kdbx$' | sort)
set TOTAL_BACKUPS (count $ALL_BACKUPS)
if test $TOTAL_BACKUPS -gt 5
set TO_REMOVE_COUNT (math $TOTAL_BACKUPS - 5)
for i in (seq 1 $TO_REMOVE_COUNT)
rm -f "$MOUNTS/$DISK/$ALL_BACKUPS[$i]"
end
end
sync
if udisksctl unmount -b /dev/disk/by-label/$DISK
notify-send --icon drive-harddisk "Security Key Synced" "Database updated (Daily copy created) and $DISK safely unmounted."
else
notify-send --icon error "Unmount Failed" "Database updated, but $DISK is still busy."
end
else
# Backup already exists for today, unmount safely without writing
echo "Backup for today ($TODAY) already exists. Skipping sync."
udisksctl unmount -b /dev/disk/by-label/$DISK
end
end
else
echo "Bootkey $DISK not found. Skipping sync."
end
Posted
16:30 01/06/2026