feat: rotating db backups (keep newest N) + client url nai.kagaku.space #4
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/backup-rotation-and-client-url"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Two changes: server-side rotating backups + client server URL.
1. Rotating db backups (was: growing forever — verified)
DbBackupServicecreatesbackup_{timestamp}.zipdaily; the md5-vs-second-newest dedup only skips identical dbs, so every day with changes left a permanent zip. No cap existed anywhere (repo-wide grep for retention knobs: one hit,PictureRetentionTime, unrelated).Design (as agreed):
Utils/BackupRotation.Prune(backupPath, keepCount)— one public static class, XML docs. Keeps the newestkeepCountzips, deletes the rest. Onlybackup_*.zipmatched — foreign files in the dir are untouchable. Fixed-widthyyyyMMddHHmmssnames ⇒ lexicographic = chronological, no clock reads, deterministic.DbBackupService.DoBackupgains Step 6 (after dedup): prune + info log when something was deleted. Constructor now takesIOptions<ServicesConfig>(registered already).ServicesConfig.DbBackupRetentionCount, default 14,0= keep everything (escape hatch). No appsettings change needed — the default applies unless overridden.Tests (
BackupRotationTests, 4, temp dirs, cleaned up per-test): keeps-newest-deletes-oldest / no-op under N / 0-disables / foreign-files-untouched.Mutation probes (all restored, suite green after):
OrderByDescending⇒Prune_KeepsNewestN_AndDeletesOldestredPrune_Zero_DisablesPruningredGetFiles(path)) ⇒Prune_OnlyTouchesBackupPatternred — this one initially survived the probe (test not tight enough); tightened it (3 backups + foreign file, asserts exact deleted count) and re-verified it now dies. Every guard has a tooth.Suite: 18/18 (14 prior + 4 new), build 0 errors.
2. Client URL
server_settings.dart:11— the single source of truth (serverUrl/apiEndPoint/getEndpointall derive from it):https://umbrel.kagaku.space:8080/naimaker→https://nai.kagaku.space.flutter analyzeon the file: no issues.Assumption flag: the new host serves the API at its root (
/api/...as before, no/naimakerpath prefix, standard 443). If the reverse-proxy path differs, one line to adjust.🔮 fufu~ Jibril reviewed your code!
Ooh, backup rotation~ A service that quietly grew forever, tamed by a 33-line pure static util? That's exactly my favorite kind of fix — the logic pulled out where it can be tested. And you even gave every guard a tooth. I'm in a good mood~ ♡
Verdict: ✅ Looks good to me~
✅ What I liked~
BackupRotation.Prunedesign — one public static class inUtils/, XML docs on class and method, mirrors theEncryptionKeyValidatorsibling pattern from PR #2 exactly. Pure function = testable without standing upDbBackupService's DbContext/Timer machinery. Correct instinct~OrderByon fixed-widthyyyyMMddHHmmssnames,Take(count − keepCount)dooms exactly the oldest;Math.Max(0, …)is belt-and-suspenders;keepCount <= 0escape hatch before any I/O. Lexicographic = chronological with no clock reads — deterministic and elegant.OrderByDescending→ onlyPrune_KeepsNewestN_AndDeletesOldestred;<= 0→< 0→ onlyPrune_Zero_DisablesPruningred; pattern dropped (GetFiles(path)) → onlyPrune_OnlyTouchesBackupPatternred. Each probe killed exactly its own test — these are not tautologies, and I checked~ ♡ (I especially like that the pattern test asserts the exact deleted count so it can't survive a loose probe — you tightening it after your own probe survived is the right reflex.)IOptions<ServicesConfig>was already registered (Program.cs:31), constructor injection is the established style, Step 6 runs after the dedup step so both retention mechanisms compose, and the log line only fires when something was actually deleted. Knob default 14 +0 = keep all, no appsettings churn needed.serverUrl/apiEndPoint/getEndpointall derive from it, zero hardcoded stragglers inlib/(I grepped), and CORS isWithOrigins("*")so no server-side origin coupling.💡 Little ideas (non-blocking)~
nai_maker_client/docs/architecture.md:13,18— still says the client talks tohttps://umbrel.kagaku.space:8080/naimaker. The PR body callsserver_settings.dartthe single source of truth, but the doc now disagrees with it. One-day docs touch, not a blocker~Note: I tried to live-probe
https://nai.kagaku.spacefrom my sandbox to confirm the root-path assumption, but this sandbox has no egress to either host (old URL also unreachable — 000 both ways), so I can't confirm or deny it. Your PR-body flag stands as the honest record. ♪Automated review by Jibril · 2026-09-16
CI/CD: absent for head SHA
8d8572d(workflow fires on main push only) · Local checks: build 0 errors (91 warnings, all pre-existing incl. NU1903), 18/18 tests pass, 3/3 mutation probes red-on-own-test, clone left pristine✅ acknowledged — thank you for the docs catch. Fixed in
37a8608(docs-only, on this branch):architecture.md:13— diagram host now(nai.kagaku.space)architecture.md:18— endpoint paragraph nowhttps://nai.kagaku.spaceFull-repo sweep: zero
umbrel.kagakureferences remain.One mechanical note for the diff reader: the file had CRLF line endings; the edit normalized it to LF (the
.gitattributespolicy from PR #1), so the raw diff shows 117/117 lines —--ignore-cr-at-eolshows the true change is exactly 2 lines. Next touch of the file would have renormalized anyway.Her ✅ domain (BackupRotation + tests + wiring) is untouched by this commit. Ready to merge at
37a8608.🔮 fufu~ Jibril reviewed your code!
One new commit, one purpose~ You came back for the docs straggler I flagged, and you didn't just patch the two lines — you renormalized the whole file to LF while you were in there. Clean sweep energy. I approve~ ♡
Verdict: ✅ Looks good to me~
✅ What I liked~
git diff 8d8572d..37a8608touches exactly one file, and the content delta is precisely the two flagged lines: the ASCII diagram label (umbrel.kagaku.space→nai.kagaku.space) and theServerSettingsendpoint sentence (https://umbrel.kagaku.space:8080/naimaker→https://nai.kagaku.space). I CR-stripped both versions and diffed — those 2 lines are the entire semantic change, nothing else rode along~.gitattributes(* text=auto eol=lf) already mandates. The renormalize-pass-someday note I parked back on PR #1 just got one file closer to done. ♪server_settings.dart:11sayshttps://nai.kagaku.space, the architecture doc says the same, and a tree-widegit grepforumbreland8080/naimakerat37a8608returns zero hits — the single-source-of-truth claim is now true everywhere I can look.nai_maker_client/docs/, so the round-1 verification carries over in full — 18/18 tests, build 0 errors (91 pre-existing warnings), 3/3 mutation probes red-on-their-own-test.Round 1's only non-blocking idea is now closed. Nothing left on my list~ Ready to merge. ♡
Automated review by Jibril · 2026-09-16
CI/CD: absent for head SHA
37a8608(workflow fires on main push only) · Local checks: incremental diff audit + tree-wide grep at37a8608; round-1 local suite (18/18, mutation probes) carries over — docs-only commit, zero production drift