| 54 |
rodolico |
1 |
## cleanSnaps
|
|
|
2 |
|
|
|
3 |
Clean old ZFS snapshots whose names include a TTL suffix. This document describes usage, accepted snapshot name patterns, options, examples and notes.
|
|
|
4 |
|
| 60 |
rodolico |
5 |
**Version:** 1.1
|
|
|
6 |
**Author:** R. W. Rodlico
|
|
|
7 |
**License:** Simplified BSD License (FreeBSD License)
|
|
|
8 |
|
| 54 |
rodolico |
9 |
### Purpose
|
|
|
10 |
|
|
|
11 |
`cleanSnaps` is a small Perl utility that scans a list of ZFS snapshots and removes or reports snapshots that have exceeded their configured TTL. TTLs are encoded in snapshot names as a numeric suffix plus a unit (s, m, h, d, w, y). The script supports date/time-stamped snapshot names and an optional time-shift for testing.
|
|
|
12 |
|
|
|
13 |
### Location
|
|
|
14 |
|
|
|
15 |
Script: `cleanSnaps` (same directory as this file)
|
|
|
16 |
|
|
|
17 |
### Synopsis
|
|
|
18 |
|
|
|
19 |
```
|
| 60 |
rodolico |
20 |
cleanSnaps [--force|-f] [--verbose|-v] [--timeshift|-t <shift>] [--unmatched|-u] [--version|-V] [--help|-h] [pool]
|
| 54 |
rodolico |
21 |
```
|
|
|
22 |
|
|
|
23 |
- `--force, -f` — Actually destroy snapshots. By default the script runs in safe (non-destructive) mode and only reports candidate snapshots.
|
|
|
24 |
- `--verbose, -v` — Verbose logging; prints keep/skip messages and extra information.
|
| 60 |
rodolico |
25 |
- `--timeshift, -t <shift>` — Apply an artificial time-shift. The script accepts a signed or unsigned quantity with a unit (examples: `30d`, `-4w`, `+3d`, `3600s`). Units: s, m, h, d, w, y. Useful for testing TTL expiry without changing the system clock. A positive shift moves "now" forward (so more snapshots appear expired); a negative shift moves "now" backward. No sign assumed to be backwards.
|
|
|
26 |
- `--unmatched, -u` — ONLY clean snapshots that don't match the retention pattern. When this flag is set, normal retention/TTL processing is skipped entirely. Only snapshots that fail to match the expected date/time + TTL pattern (or have unparseable dates) are marked for removal. This is useful for cleaning up snapshots with incorrect naming conventions.
|
|
|
27 |
- `--version, -V` — Show version information and exit.
|
| 54 |
rodolico |
28 |
- `--help, -h` — Show help/usage and exit.
|
|
|
29 |
- `pool` (optional) — Restrict processing to snapshots whose pool name matches this string.
|
|
|
30 |
|
|
|
31 |
When called without a positional `pool` argument, the script will process snapshots from standard input or from a `zfs list` invocation performed by the script. (See the script headers and tests for details.)
|
|
|
32 |
|
|
|
33 |
### Snapshot name format
|
|
|
34 |
|
|
|
35 |
The script recognizes snapshots whose names contain a date stamp followed (optionally after arbitrary text) by a TTL token. The accepted date/time formats include:
|
|
|
36 |
|
|
|
37 |
- `YYYY-MM-DD` (e.g. `2025-12-15`)
|
|
|
38 |
- `YYYY-MM-DD_HH:MM:SS` or `YYYY-MM-DD_HH.MM.SS` (time portion may use `:` or `.` separators; the separator between date and time is an underscore)
|
|
|
39 |
|
|
|
40 |
The TTL suffix is a number followed by a unit letter:
|
|
|
41 |
|
|
|
42 |
- `s` — seconds
|
|
|
43 |
- `m` — minutes
|
|
|
44 |
- `h` — hours
|
|
|
45 |
- `d` — days
|
|
|
46 |
- `w` — weeks
|
|
|
47 |
- `y` — years
|
|
|
48 |
|
|
|
49 |
Examples of matching snapshot name fragments:
|
|
|
50 |
|
|
|
51 |
- `backup_2025-12-15_16.09.00_7d` — date/time + TTL 7 days
|
|
|
52 |
- `autosnap_2025-12-15_7h` — date + TTL 7 hours
|
|
|
53 |
- `daily_2025-12-15_30d_old` — optional trailing text after TTL is allowed; the TTL token (e.g. `30d`) is used to compute expiry.
|
|
|
54 |
|
|
|
55 |
The script tolerates arbitrary text between the date/time stamp and the TTL token.
|
|
|
56 |
|
|
|
57 |
### Matching regex
|
|
|
58 |
|
|
|
59 |
The script uses a single regular expression to identify date/time stamped snapshot names and their TTL token. The exact regex is:
|
|
|
60 |
|
|
|
61 |
```
|
|
|
62 |
/.*?(\d{4}-\d{2}-\d{2}(?:[T _]\d{2}[:\.]\d{2}(?:[:\.]\d{2})?)?)(?:.*?)(\d+)([smhdwy])$/
|
|
|
63 |
```
|
|
|
64 |
|
|
|
65 |
Explanation:
|
|
|
66 |
- The regex is applied to the snapshot name portion (the part after the `@` in a `dataset@snapshot` string).
|
|
|
67 |
- Capture group 1: the date or date-time stamp. Accepts `YYYY-MM-DD` or `YYYY-MM-DD_HH:MM`/`YYYY-MM-DD_HH.MM.SS` styles. The separator between date and time can be `T`, space, or underscore; time separators may be `:` or `.`.
|
|
|
68 |
- Capture group 2: the numeric TTL quantity (one or more digits).
|
|
|
69 |
- Capture group 3: the TTL unit, one of `s m h d w y` (seconds, minutes, hours, days, weeks, years).
|
|
|
70 |
- The regex allows arbitrary text between the date/time stamp and the TTL token, and requires the TTL token to be at the end of the snapshot name.
|
|
|
71 |
|
| 60 |
rodolico |
72 |
If a snapshot name does not match this pattern it will be ignored by `cleanSnaps` (unless the `--unmatched` flag is used).
|
| 54 |
rodolico |
73 |
|
|
|
74 |
### Behavior
|
|
|
75 |
|
| 60 |
rodolico |
76 |
**Normal mode (without --unmatched):**
|
|
|
77 |
- The script calculates the expiry time as the date/time parsed from the snapshot name plus the TTL interval. If the resulting expiry timestamp is earlier than the current time (adjusted by any `--timeshift`), the snapshot is considered expired and will be listed.
|
|
|
78 |
- Snapshots that don't match the pattern are skipped and reported in verbose mode.
|
|
|
79 |
- Use `--force` to perform destruction via `zfs destroy`.
|
|
|
80 |
|
|
|
81 |
**Unmatched mode (with --unmatched):**
|
|
|
82 |
- The script ONLY processes snapshots that fail to match the expected pattern.
|
|
|
83 |
- Normal retention/TTL logic is completely skipped for snapshots that match the pattern.
|
|
|
84 |
- Only snapshots with names that don't match the date/time + TTL regex (or have unparseable dates) are marked for removal.
|
|
|
85 |
- This mode is useful for cleaning up incorrectly named snapshots while preserving all properly formatted ones.
|
|
|
86 |
|
|
|
87 |
**Both modes:**
|
| 54 |
rodolico |
88 |
- The script prints actions it would take; in its default (safe) mode it only reports candidate snapshots. Use `--verbose` for additional keep/skip messages and context.
|
|
|
89 |
|
|
|
90 |
### Examples
|
|
|
91 |
|
| 60 |
rodolico |
92 |
**Normal retention mode:**
|
| 54 |
rodolico |
93 |
|
| 60 |
rodolico |
94 |
- Run with a 30-day backward time-shift (simulate time 30 days earlier, ie will retain an additional 30 days of snapshots):
|
|
|
95 |
|
|
|
96 |
```bash
|
|
|
97 |
cleanSnaps -t 30d # same as -30d
|
| 54 |
rodolico |
98 |
cleanSnaps -t 2592000 # equivalent (30 days in seconds)
|
|
|
99 |
```
|
|
|
100 |
|
| 60 |
rodolico |
101 |
- Actually remove all expired snapshots from pool tank, displaying each step:
|
| 54 |
rodolico |
102 |
|
| 60 |
rodolico |
103 |
```bash
|
| 54 |
rodolico |
104 |
cleanSnaps -f -v tank
|
|
|
105 |
```
|
|
|
106 |
|
|
|
107 |
- Restrict processing to pool `tank` (only snapshots whose pool prefix matches `tank` will be considered):
|
|
|
108 |
|
| 60 |
rodolico |
109 |
```bash
|
| 54 |
rodolico |
110 |
cleanSnaps tank
|
|
|
111 |
```
|
|
|
112 |
|
| 60 |
rodolico |
113 |
**Unmatched snapshot cleanup:**
|
| 54 |
rodolico |
114 |
|
| 60 |
rodolico |
115 |
- List all snapshots that don't match the expected naming pattern:
|
|
|
116 |
|
|
|
117 |
```bash
|
|
|
118 |
cleanSnaps -v -u
|
| 54 |
rodolico |
119 |
```
|
| 60 |
rodolico |
120 |
|
|
|
121 |
- Actually remove snapshots with incorrect names (useful for cleaning up after naming convention changes):
|
|
|
122 |
|
|
|
123 |
```bash
|
|
|
124 |
cleanSnaps -f -u tank
|
| 54 |
rodolico |
125 |
```
|
|
|
126 |
|
| 60 |
rodolico |
127 |
**Version and help:**
|
|
|
128 |
|
|
|
129 |
- Show version information:
|
|
|
130 |
|
|
|
131 |
```bash
|
|
|
132 |
cleanSnaps --version
|
|
|
133 |
cleanSnaps -V
|
|
|
134 |
```
|
|
|
135 |
|
|
|
136 |
- Show help:
|
|
|
137 |
|
|
|
138 |
```bash
|
|
|
139 |
cleanSnaps --help
|
|
|
140 |
cleanSnaps -h
|
|
|
141 |
```
|
|
|
142 |
|
| 54 |
rodolico |
143 |
### Safety & Notes
|
|
|
144 |
|
|
|
145 |
- The script includes a help flag and a Simplified BSD license header. It is safe by default (it reports candidates without destroying them). Nevertheless, exercise caution when running on production systems — prefer testing with `--timeshift` or sample input first.
|
| 60 |
rodolico |
146 |
- In normal mode, the script parses a variety of date/time formats but will ignore snapshot entries that do not match the expected patterns; those snapshots are left untouched.
|
|
|
147 |
- The `--unmatched` flag inverts this behavior: it ONLY targets snapshots that don't match the pattern, making it safe to use for cleaning up incorrectly named snapshots without affecting your properly formatted ones.
|
| 54 |
rodolico |
148 |
- The script depends on standard Perl modules available in most Perl 5 installations. See the script header for exact module imports.
|
| 60 |
rodolico |
149 |
- Always run without `--force` first to review what would be deleted.
|
| 54 |
rodolico |
150 |
|
|
|
151 |
### Tests
|
|
|
152 |
|
| 60 |
rodolico |
153 |
There is a test harness in the repository (see `cleanSnaps/test_cleanSnaps.pl`) which includes sample snapshot names covering date/time formats, dot/colon time separators, and the `h` (hour) TTL unit. The test suite includes:
|
| 54 |
rodolico |
154 |
|
| 60 |
rodolico |
155 |
1. **TEST 1**: Validates normal retention logic - snapshots are correctly kept or removed based on TTL
|
|
|
156 |
2. **TEST 2**: Validates `--unmatched` flag - only unmatched snapshots are removed, matched snapshots are skipped
|
|
|
157 |
3. **TEST 3**: Validates `--version` flag - version information is displayed correctly
|
|
|
158 |
|
|
|
159 |
Run the test with: `perl test_cleanSnaps.pl`
|
|
|
160 |
|
|
|
161 |
Use the test to validate behavior before running against real ZFS pools.
|
|
|
162 |
|
|
|
163 |
You can also include a filename which contains a list of snapshots and all tests will be run against that.
|
|
|
164 |
|
| 54 |
rodolico |
165 |
### Contributing / Changes
|
|
|
166 |
|
|
|
167 |
If you update the script, update this documentation to reflect new options, changes in the snapshot matching regex, or different behaviors for dry-run vs. delete modes.
|
|
|
168 |
|
|
|
169 |
### License
|
|
|
170 |
|
|
|
171 |
The script and this documentation are released under the Simplified BSD License (see header in `cleanSnaps`).
|