Preserve older radar days on a second volume¶
I want enough headroom for the live radar and its native archive without removing
observations. This procedure copies explicitly selected closed days to /data,
verifies every file, and keeps the same paths under /project/aurora/raw/rpgfmcw94
with persistent read-only bind mounts. The original days stay in quarantine until
the mounted copies and consumer dependencies have been checked.
This is an operator procedure, not an automatic retention policy. Preparing the helper does not copy data, change mounts, restart a service or release an original. Preparation installs inert launch conditions and reloads systemd; it does not create the temporary operator hold. Keep native backfill paused until the final seven-day capacity check passes.
At 09:29 UTC on 24 October, cutover was deferred after all five days had staged. The temporary reader barrier was removed and the recorded schedules restored; all original paths and verified staged copies remain intact. A separate old-quarantine preservation procedure can restore destination headroom using existing root capacity; it does not authorize this cutover. See the dated outcome.
The 24 October plan¶
The first reviewed candidates are Y2026/M05/D02 through D05: 96 regular files per day,
384 files in total and 369,295,809,536 allocated bytes (343.94 GiB). At inspection
they had no symlinks, hard links, submounts or open file descriptors. Their newest
modification times were 5–6 May; metadata last changed in July. Those observations
are a reason to consider them closed, not a substitute for the copy-time checks.
The plan also includes only Y2026/M05/D13, with an explicit 64-file expectation:
16 files of each native type, 61,415,642,624 allocated bytes. This was a partial
historical acquisition day; changing its expectation does not weaken the 96-file
checks for D02–D05. Two source metadata inventories were identical at 08:04:39 and
08:05:11; the newest file mtime was 14 May. Both open-file and process-map scans
found no reader, and there were no links or submounts. All five days total
430,711,452,160 allocated bytes and 448 files. Queue D13 only after the current
four-day stage finishes; do not run heavy stage copies in parallel.
The destination is /data/aurora/raw-relocated/rpgfmcw94/<day>. The temporary
original is /project/aurora/recovery/radar-relocation-20261024/<day>, outside the
raw archive tree. Archive relative paths and file modification times stay the same.
The raw GWS rsync, object-store copy and archive dispatch follow these directory
binds; none uses a one-filesystem restriction. They retain full native archive
coverage. The DO mirror has a separate, selected scope: its raw-rpgfmcw94 stage
excludes Y2026/** and .radar-partials/**, while the dashboard receives compact
radar products and quicklooks. Keep those exclusions. This relocation must not
start a historical native radar copy to DO.
The dated plan is inventory/radar-relocation-20261024.example.yml. Re-measure both
volumes before copying and cutover. The recorded arrival rate was 84,856,499,251
bytes/day; seven future days plus 8 GiB and four largest in-flight files require
617,088,629,797 free bytes on /project after delivery of existing debt. The fresh
08:06 source comparison found 44 missing native files totalling 51,015,441,637
bytes. That first plan required 668,104,071,434 bytes before native backfill.
The four original days alone would not meet this post-backlog headroom gate.
At 08:06, the five-day projection left 709,263,569,408 bytes on /project, or
658,248,127,771 after the known backlog: 41,159,497,974 above the future seven-day
target. /data projected 58,176,908,800 bytes after all remaining copies, exceeding
its separate 40 GiB reserve by 15,227,235,840 bytes. Already staged allocations were
subtracted once, including partial D03. These are dated estimates, not permission
to spend future free space. Recheck allocation, arrival rate, debt, quota and actual
available bytes before D13 staging, cutover and backfill. Keep backfill held until
its fresh post-backlog capacity calculation passes.
The 08:53 refresh found 54,622,066,409 bytes of native debt, raising the dated
primary minimum to 671,710,696,206 bytes. The active archive recovery has a stricter
50 GiB /data floor, so the plan now uses that instead of the earlier 40 GiB.
Preparation rejects a lower reserve when archive recovery is enabled. Re-read its
actual configured reserve and use the stricter requirement before any copy or cutover.
Also reserve measured non-staging growth for the remaining copy, consumer drain
and full mounted-verification window. Track /data free bytes plus the actual
allocated stage bytes to distinguish other growth from copying. Sample free space
after counting pending copy bytes; a concurrent completed copy must not be credited
against an older free-space reading. Credit source allocation only while every
selected day remains unmounted on the original /project filesystem. After release,
use actual free space without adding the moved bytes again. If the time allowance
would cross the reserve, hold the next stage or cutover and report the shortfall.
Do not weaken the floor or assume future cache cleanup.
The operation window is only one capacity check. Before activating the mounts,
also establish sustained headroom on both volumes, including restored writers,
native delivery debt and continuing product growth. The active relocation guard
would block the twelve named consumers below the destination reserve. Passing
the /project seven-day calculation while /data is close to its floor is not
enough. ASS retention affects the edge system; enabling it does not create primary
/data capacity. If this longer-term gate is unresolved, defer cutover, preserve
both copies and restore only the schedules recorded by the temporary barrier.
expected_files_by_day permits a positive exact count only for an explicitly
listed day. The reviewed D13 override leaves all other days at 96. No other older
day is included in this plan.
Prepare and stage¶
Use an exact reviewed infrastructure commit reachable from main. Preserve the installed helper and wrapper checksums in the dated recovery evidence.
uv run ansible-playbook playbooks/radar_raw_relocation_prepare.yml \
--limit aurora-cloud -e @inventory/radar-relocation-20261024.example.yml --check --diff
uv run ansible-playbook playbooks/radar_raw_relocation_prepare.yml \
--limit aurora-cloud -e @inventory/radar-relocation-20261024.example.yml
On JASMIN, inspect the complete plan and stage one day at a time:
sudo /usr/local/lib/aurora-radar-relocation.py inspect \
--config /etc/aurora/radar-relocation-plan.json
sudo /usr/local/lib/aurora-radar-relocation.py stage \
--config /etc/aurora/radar-relocation-plan.json --day Y2026/M05/D02
Repeat the stage command for D03, D04 and D05, then D13 after refreshing both
volume gates. It copies bytes, owner, group, mode,
modification time and extended attributes, then independently reads SHA-256 from
both files. It checks source identity and file-set stability and fsyncs the copy
and manifest. Existing complete copies are verified before reuse. An interrupted
.copying-* file is retained and blocks a retry: inspect and preserve it separately
before restarting that day. Unknown destination files are never overwritten.
Staging leaves the current paths untouched. Normal live collection may continue. Do not run simultaneous stage commands for the same day.
Before cutover, install the updated transfer guard through the narrow radar guard
playbook while its source timer is paused. Enable
aurora_standby_raw_relocation_guard: true for the DO transport, using the exact
installed wrapper checksum with playbooks/mirror_transport_release.yml. Prepare
the primary helper first. That remote guard is harmless before activation and
holds a shared lock in its parent process until each raw radar sender exits.
This includes privileged senders: sudo closes inherited file descriptors, so
passing the lock into sudo is insufficient. The parent forwards termination
signals to the sender group, waits for completion and returns its status without
adding output to the rsync protocol. A missing nested mount then
fails the transfer without advancing a mirror success stamp. Persist the setting
in the host inventory when commissioned.
If a sender supervisor is killed forcibly or crashes, inspect and drain any remaining remote rsync processes before cutover; an unlocked file alone is not proof that an orphaned reader has stopped.
Cut over with the originals still present¶
Record timer and service states before verification, including the recovery
launcher (aurora-object-store-recovery.service and its timer) and DO's raw radar
stage. Preserve pre-existing failed states; do not use reset-failed.
The plan lists twelve raw readers: live and bulk radar sync, appender, quicklooks,
archive dispatch, both raw archive copies, mirror verification, retention, the raw
recovery worker, shared repair, and recheck-after-repair. The latter two can start
from worker completion hooks; a single inactive check does not close that race.
Preparation installs a separate negative ConditionPathExists drop-in for each
reader. The absent /etc/aurora/radar-relocation.hold leaves them unchanged.
After checking the fresh stage proofs, create that persistent operator hold before draining. Pause the recovery launch timer and wait for its short coordinator service to finish, so condition-skipped launches do not add noise to the durable queue. Keep existing product verification workers and recovery-manifest upload running. The full and incremental inventory units may remain enabled only after their actual live commands have been checked: they must enqueue requests, not directly walk raw files. Keep their queue and checkpoint files untouched.
Pause only the recorded raw timers and drain all twelve readers. Coordinate DO's
raw-rpgfmcw94 timer and inspect remote rsync processes separately from the shared
lock. The helper checks the hold, unchanged condition drop-ins, loaded systemd
configuration, inactive/drained consumers and recovery launcher before changing
paths. All twelve readers also receive mount Requires, After, BindsTo and a
failing ExecStartPre guard at cutover. Existing commands and environments stay
unchanged.
Keep retention held throughout. Its independent persistent
/etc/aurora/ass-retention.hold remains while either ass_retention_enabled or
ass_retention_timer_enabled is false, including direct archive-triggered starts.
Use playbooks/retention_hold.yml for a narrow deployment; it does not run or reset
retention. The full role removes that hold only when both switches are explicitly
enabled after their separate gates pass.
A completed stage is already a full SHA-256 verification of both copies. When all five completed manifests are less than six hours old, cutover can use those proofs: under the exclusive lock it checks unchanged source and destination file sets and identities before the first rename. There is no need to repeat the same full read before a reversible path change. The originals remain in quarantine.
If a proof is older, run full verification while readers and collection continue, then investigate any changed identity rather than bypassing it:
sudo /usr/local/lib/aurora-radar-relocation.py verify \
--config /etc/aurora/radar-relocation-plan.json
That command holds a shared lock. Cutover cannot race it. A complete additional five-day read would read about 861.42 GB across both copies; the dated estimate is 60–100 minutes, separate from staging and dependent on storage/cache throughput. The full read after mounting remains mandatory before releasing any original.
Then create the temporary barrier, pause the recovery launcher and recorded raw timers, drain readers and perform the short cutover. Record ownership of the hold in the dated evidence and fsync its file and parent directory before proceeding:
sudo python3 - <<'PYHOLD'
import os
from pathlib import Path
path = Path('/etc/aurora/radar-relocation.hold')
# Exclusive creation refuses to overwrite somebody else's operation.
with path.open('x') as stream:
stream.write('Reviewed radar relocation, 2026-10-24\n')
stream.flush()
os.fsync(stream.fileno())
fd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY)
os.fsync(fd)
os.close(fd)
PYHOLD
sudo systemctl stop aurora-object-store-recovery.timer
# Wait for the coordinator, current raw readers and remote senders to drain.
Then:
sudo /usr/local/lib/aurora-radar-relocation.py cutover \
--config /etc/aurora/radar-relocation-plan.json
sudo /usr/local/lib/aurora-radar-relocation.py guard
sudo /usr/local/lib/aurora-radar-relocation.py graph
Cutover repeats the reserve checks and compares every source/destination identity,
size, modification time and change time with the completed SHA verification. The
proof must be less than six hours old. It publishes the active guard record,
renames each original into quarantine, and enables a persistent read-only bind
mount. Each mount requires both project.mount and data.mount. The active guard
record is written before the first rename: an interruption therefore blocks raw
consumers instead of exposing an empty tree as success. Timers remain paused.
Each bind unit asserts that its destination directory already exists; systemd must not create an empty replacement. The consumer guard also compares the exact file set and file identities, sizes, times and ownership against the completed SHA manifest. These are metadata reads, not another full archive hash. A missing, empty or changed destination fails even when the bind itself is mounted read-only.
Before releasing anything, compare every logical path with its destination, run
systemd-analyze verify on the generated mount and consumer units, inspect their
Requires/After/BindsTo properties, and test missing-mount rejection with a
separate disposable test plan. Test the dependency stop behavior using disposable
mount/service units, never by unmounting a production volume. No reboot is needed.
Check a normal read through the original logical path and both raw archive tools'
read-only discovery. Preserve that evidence alongside the per-file manifests.
Release only verified duplicate originals¶
Run verify with the active configuration while readers are running; the
logical copies are read-only. It checks the mount guard before and after reading,
hashes every logical bound path and its quarantined original, and rejects a
changed target or active cutover record during that read. Only a fully successful
pass publishes a durable mounted verification receipt tied to the exact manifest,
active cutover record and logical/destination directory identities. The active
record is bound by SHA-256 and filesystem identity, so a new cutover requires new
mounted verification even if its plan is identical. An interrupted pass leaves
the previous proof intact.
A staging-only proof cannot authorize release. An equivalent same-target remount
with unchanged files remains valid; the normal guard still requires the correct
read-only bind. Then recreate the same owned launch hold and pause/drain consumers
and the recovery launcher for the short release.
release checks active mounts, read-only options, both reserves, the dependency
graph, mounted verification receipt and unchanged file identities against that
full verification. It repeats the receipt/cutover identity check before each
unlink. It writes durable per-file release intent and completion records before
and after unlinking only the duplicate files in the named quarantine day. It never removes the
logical raw observation or the relocated copy.
sudo /usr/local/lib/aurora-radar-relocation.py verify
# Pause and drain the recorded consumers after verification completes.
sudo /usr/local/lib/aurora-radar-relocation.py release --day Y2026/M05/D02
Repeat for D03–D05 and D13 only after the checks above pass. If a release is interrupted,
keep the remaining quarantine files. Re-run verify, then resume the same
release --day command. A missing original is accepted only if its durable
release intent or completion record exists; an unexpected missing file still
fails. Rollback refuses after the first release intent. Do not use recursive
deletion to finish a release.
After each successful mount/graph/read-through check, remove only the temporary
relocation hold owned by this operation and restore the recorded raw timer states.
Leave the independent retention hold in place. Replay one ordinary recovery tick
and one shared repair start to process durable queued requests and the latest
report. A condition-skipped raw launch remains launching for at most 120 seconds;
reconciliation preserves its remaining confirmations, epoch, checkpoint and retry
attempt count. The coordinator pause avoids those skipped launches during cutover.
After release, verify actual free space on both volumes and two complete raw archive reports. Restore the timers that were previously running. Keep retention and native backfill held until their own archive and seven-day headroom gates pass. The live LV1 timer remains independent of complete native delivery.
Rollback¶
Before any original is released, activate the same temporary hold, pause/drain the same consumers and recovery launcher, then run:
This also handles an interrupted cutover with a missing or failed bind. Rollback
verifies the quarantined originals and destination directly; it does not falsely
claim a successful mounted publication or require a healthy mount to recover.
It removes only
its own unchanged mount/drop-in definitions, stops the binds, and renames the
original directories back. The /data copies remain preserved. Restore the
recorded timer states separately. After a duplicate original has been released,
rollback requires a verified copy back and enough /project headroom; the helper
refuses to pretend the old copy still exists.
See radar source sync, archive services and the dated recovery record.
Withdraw unused staging copies¶
If cutover is deferred, I can recover the space used by this operation's redundant
staging copies without moving an original observation. This is a separate action
from release and rollback: neither of those commands withdraws an unused stage.
The original raw days remain authoritative, and native backfill and retention
keep their independent holds.
aurora-radar-unstage.py requires a reviewed JSON request containing
operation_id, the absolute config and helper paths, plan_sha256,
helper_sha256, and a manifests mapping from every plan day to its stage-manifest
SHA-256. Pin the newly installed helper, including its withdrawal guard. Install
only that reviewed helper and the withdrawal tool, preserving the previous helper;
this action does not require a role deployment or any unit changes.
sudo python3 /usr/local/lib/aurora-radar-unstage.py \
--request /root/radar-stage-withdrawal-request.json
# Apply only after reviewing the complete validation result.
sudo python3 /usr/local/lib/aurora-radar-unstage.py \
--request /root/radar-stage-withdrawal-request.json --apply
The default command validates without creating a journal or removing files. Both commands take the existing exclusive relocation lock without waiting. A busy lock means no withdrawal was attempted; let the existing sender finish. If a brief schedule pause is necessary to obtain a window, review and record that separately. The tool never stops a process, timer or service.
Before the first removal, every day must have a paired SHA verification less than six hours old and unchanged complete original and destination file inventories. The tool checks device, inode, size, modification and change times, ownership, permissions, file type and link count. It refuses an active relocation, quarantine, release ledger, mounted verification, launch barrier, selected-tree mount or submount, active/queued staging unit, unknown open user, or incomplete process scan. The process scan covers descriptors, memory maps, current directories and roots, including device/inode aliases. These checks reuse the completed, fsynced SHA proof; they do not adopt new file identities or rehash changed observations automatically.
The root-owned stage-withdrawal.json journal pins the reviewed request, tool,
helper, plan, all manifests and source/destination directory identities. Each
removal has durable intent, an unlink relative to the opened destination directory,
a directory fsync and durable completion. Original entries and current directory
identities are checked again immediately before that unlink. Unknown or changed
files stop the action. All original files, stage manifests and empty target day
directories remain in place.
For an interrupted action, keep the exact request and journal and use --resume
--apply. A missing copy is accepted only for that journal's pending or completed
entry. Missing, replaced or malformed evidence fails closed; do not repair it by
hand or run recursive removal. An expired incomplete proof needs a separately
reviewed recovery decision. A completed rerun only verifies and reports completion;
it does not need a new freshness window because it removes nothing.
As soon as the journal exists, stage, verify and cutover refuse the old plan,
including after an interrupted withdrawal. Keep the journal permanently. Any
future relocation needs a separately reviewed plan and new state directory. After
completion, record actual free space on both volumes and ordinary reader/source
results. Withdrawing copies restores /data; it does not itself satisfy the
independent /project native-backfill capacity gate.