NAME
autocleansnap - expire ZFS snapshots according to the retention policies
SYNOPSIS
/opt/omnicube/backup/autocleansnap [ZONE]
/opt/omnicube/backup/autocleansnap -h
DESCRIPTION
autocleansnap is the expiry half of the OmniCube snapshot lifecycle. Driven from cron(8) once an hour, after autosnap(8) and autosync(8), it counts the snapshots of each type on every dataset flagged for snapshotting, destroys the oldest ones beyond the retention count, and removes their counterparts on the backup host when the policy defines a deterministic remote target.
Unlike the other two, it is not bound to a single SMF instance. It enumerates every instance of svc:/omnicube/sysadm with svcs -H -o fmri and collects each one's property prefix from its config/site property. Disabled instances are included deliberately: the snapshots an instance created stay protected by its policies until the instance is deleted, not merely disabled. An instance without config/site is skipped with a warning; if none remains usable the command exits 78.
For each prefix, datasets are listed with
zfs list -H -r -t filesystem,volume -o name,${PREFIX}:snapshotting
keeping the exact value yes. The results are unioned, and the prefixes that selected a dataset become its managing prefixes, which drive both the policy set and the safety checks below. A dataset is skipped when ${PREFIX}:is_locked is true under any prefix, so cleanup never races an in-flight autosnap(8) or autosync(8) of any instance; the run-level guard is re-evaluated before each dataset.
Policy merge
The policies named by ${PREFIX}:snappolicy across the managing prefixes are uniqued (an unset value or - normalises to default), resolved by get_snap_policy_path() and sourced in turn; see default.conf(5). Retention is merged, remote targets are not: MERGED_RETENTION[TYPE] is the highest (most conservative) value any applicable policy defines, while BKPUSER, BKPHOST, BKPZP, BKPDS, IS_SYNC and BKPPRIV stay per policy, each policy with a deterministic target contributing one remote destroy per snapshot deleted locally. A non-numeric RETENTION value in any policy marks the type bad and skips cleanup of that type on that dataset with a warning, instead of destroying everything as a silent arithmetic zero.
Types are processed in the order yearly, weekly, daily, hourly, frequent, and a snapshot belongs to a type when its name contains @TYPE. The merged count is read as: unset, no policy mentions the type, so existing snapshots are left alone as they may be managed elsewhere; 0, an explicit "keep zero", so every snapshot of the type is a candidate; and N, keep at most N and destroy the oldest surplus first. The shipped default.conf asks for weekly=1, daily=4, hourly=6, frequent=0 and yearly=0.
Safety guards
A snapshot is destroyed only when it is not needed as an incremental base by a replication that has not finished, checked under every managing prefix. In the normal case the next (newer) snapshot must be usable as the new base under every prefix, that is ${PREFIX}:bckcandel or ${PREFIX}:insync is yes, or the prefix does not remote-back it (remotebackup not yes and bckcandel not no); otherwise the snapshot is kept and "ongoing sync or backup" is logged.
When there is no newer snapshot the strict guard applies: every prefix with remotebackup=yes must positively confirm the snapshot with bckcandel=yes or insync=yes, so one remote-backed instance still waiting for its sync vetoes the deletion, while purely local instances never block cleanup. There is deliberately no loose fallback here, because an earlier version could destroy the last remaining, never-replicated snapshot.
Remote cleanup
With BKPZP the remote target is BKPZP/ plus the local path after the first /zones/ segment; with BKPDS and IS_SYNC=1 it is BKPDS with the local top-level pool replaced. The legacy BKPDS container mode of autosync(8) is not handled, since its target depends on remote state, and those remote snapshots are left to their own cycle. An IS_SYNC that is neither 0 nor 1 is reported and treated as 0. For each target ssh_reachable() runs first, so an unreachable backup host makes cleanup skip loudly instead of orphaning the remote snapshot forever; existence is probed unprivileged with zfs list, and only the zfs destroy carries the policy's BKPPRIV (reduced to a bare command name by _bkp_priv()). A validation failure disables remote cleanup for that policy only: local cleanup always continues.
Locking and the is_locked trap
One instance runs at a time, using ${LOCK_BASE}/${logtag}.autocleansnap.lock via acquire_lock_or_exit(); a second concurrent invocation exits 0 silently. While a dataset is processed its top-level zpool is marked ${PREFIX}:is_locked=true under every configured prefix, so that any instance's autosnap(8) or autosync(8) backs off. An EXIT HUP INT TERM trap removes the lock directory and inherits is_locked away under every prefix: a kill between the zfs set and the zfs inherit would otherwise leave the property stuck at true, which permanently wedges all three jobs on that pool.
OPTIONS
There is no --instance option: this command always spans every configured SMF instance.
OPERANDS
USAGE
The sample crontab in /opt/omnicube/share/README runs it hourly at 36 minutes past, thirty minutes after autosnap(8) and between the two autosync(8) passes:
36 * * * * /opt/omnicube/backup/autocleansnap >/dev/null
Clean one zone only, and watch what gets destroyed:
pfexec /opt/omnicube/backup/autocleansnap zethosmariadb01
Protect one snapshot from expiry, then release it again (property prefix omnicube here):
pfexec zfs set omnicube:bckcandel=no dpool/zones/VM@daily-26-31-1 pfexec zfs set omnicube:bckcandel=yes dpool/zones/VM@daily-26-31-1
Review the retention actually in force for a dataset:
zfs get -H -o value omnicube:snappolicy dpool/zones/VM /usr/bin/ggrep RETENTION /opt/omnicube/backup/policies/default.conf
EXIT STATUS
FILES
ENVIRONMENT
SECURITY
Local destroys and property changes use pfexec, driven by /etc/security/exec_attr.d/omnicube and /etc/security/prof_attr.d/omnicube. Policy values that reach a remote command line are validated first, and BKPPRIV is restricted to a bare command name. Policy files are sourced as bash, so they must be root-owned and not group or world writable.
SEE ALSO
autosnap(8), autosync(8), sync_pool.sh(8), default.conf(5), oc_lock(3), oc_policy(3), oc_ssh(3), oc_validate(3), omnicube_utils(3), omnicube(7).
NOTES
Adding an SMF instance immediately makes its policies protective for every dataset it manages, and its retention counts merge upwards: raising retention in one instance keeps snapshots for all of them, while lowering it has no effect as long as another instance asks for more.
Snapshots are destroyed in the order zfs list returns them, which for the names generated by autosnap(8) is the creation order. Hand-made snapshots whose names sort differently may be expired in an unexpected order.
Deleting a local snapshot also deletes its remote counterpart when the policy has a deterministic target, so the backup host is not an archive with a longer horizon than the local pool. Use a separate policy with its own retention for that.