feat(nixos): manage scattered trash dirs with trash-cli retention timer (#265)

* feat(nushell): add trash command using the home trash can

On a tmpfs root with persistent dirs bind-mounted in, `rm --trash`
scatters items into per-mount .Trash-$uid dirs that file managers never
show, and gio refuses to trash on those internal mounts.

Add a `trash` command implementing the freedesktop trash spec
'failsafe' mode: items are moved to ~/.local/share/Trash with
spec-compliant .trashinfo entries, so Thunar can list and restore them.

Also preserve ~/.local/share/Trash on idols-ai so the trash survives
reboots.

* feat(nixos): manage scattered trash dirs with trash-cli retention timer

Replace the hand-written nushell trash command with the established
trash-cli tooling. On a tmpfs root with persistent dirs bind-mounted
in, the trash crate (nushell rm --trash) scatters items into per-mount
.Trash-$uid dirs that file managers never show and nothing cleans up.

- add trash-cli to system packages: trash-list scans/trash-restore
  handles every mount point's trash dir
- daily systemd timer runs 'trash-empty 30 -f', purging items older
  than 30 days across the home trash and all mount points
- drop the hand-written trash.nu module
- note in preservation.nix: do NOT persist ~/.local/share/Trash; a
  bind-mounted trash dir breaks the trash crate's home-topdir match
  for files straight under $HOME (it would try /.Trash-$uid, EACCES)

* fix(nixos): order trash-empty after preservation.target

The scattered .Trash-$uid dirs live inside the preservation bind
mounts, which use DefaultDependencies=no and are therefore NOT ordered
after local-fs.target. Add preservation.target to After= so trash-empty
only runs once all those mounts are up.
This commit is contained in:
ryan4yin | 二花
2026-08-23 23:40:53 +08:00
committed by GitHub
parent 9df14d8c39
commit 6e0d44c50d
3 changed files with 70 additions and 0 deletions
+1
View File
@@ -86,5 +86,6 @@
which
tree
tealdeer # a very fast version of tldr
trash-cli # freedesktop trash can CLI: trash-list / trash-restore / trash-put / trash-empty
];
}
+61
View File
@@ -0,0 +1,61 @@
{
# ==================================================================
#
# Trash retention cleanup (nixpkgs `trash-cli`)
# https://github.com/andreafrancia/trash-cli
#
# On a tmpfs root with persistent dirs bind-mounted in, the trash
# crate (nushell `rm --trash`, GUI file managers) scatters items into
# per-mount .Trash-$uid dirs that file managers never show. This timer
# purges trash items older than RETENTION_DAYS across the home trash
# and all mount points.
#
# Usage:
# trash-list # view items in all trash dirs
# trash-restore <file> # restore an item
# trash-rm <file> # remove a single item from the trash
# trash-empty 30 -f # run the retention purge manually
#
# ==================================================================
myvars,
pkgs,
...
}:
let
retentionDays = 30;
in
{
systemd.services.trash-empty = {
description = "Purge trash items older than ${toString retentionDays} days";
script = ''
${pkgs.trash-cli}/bin/trash-empty ${toString retentionDays} -f
'';
# The scattered .Trash-$uid dirs live inside the preservation bind mounts, so we
# must run after preservation.target (reached only once all those mounts are up).
# preservation.mount units use DefaultDependencies=no, so they are NOT ordered
# after local-fs.target on their own; list both to be safe on every host.
# On hosts without preservation, the After=preservation.target edge is a no-op.
after = [
"local-fs.target"
"preservation.target"
];
serviceConfig = {
Type = "oneshot";
User = myvars.username;
Environment = [
"HOME=/home/${myvars.username}"
"XDG_DATA_HOME=/home/${myvars.username}/.local/share"
];
};
};
systemd.timers.trash-empty = {
description = "Timer for the trash-empty service";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "daily";
RandomizedDelaySec = "15min";
};
};
}