Skip to content

Crash-safe credentials, safer Garmin relogin, Eufy and Strava host fallbacks; 1.14.0 - #74

Merged
sturimcode merged 21 commits into
mainfrom
release/1.14.0
Oct 2, 2026
Merged

sturimcode merged 21 commits into
mainfrom
release/1.14.0

Conversation

@sturimcode

Copy link
Copy Markdown
Owner

Reliability release.

  • Credential vault: keychain saves are crash-safe (generation-tagged chunks, header switched last, checksummed), a damaged vault now raises instead of reading as empty and being saved over, and every credential write holds a dedicated file lock so concurrent runs take turns.
  • Commands that change credentials or config (--setup-strava, --use-file-store, --use-keychain, --select-profile, --update, --uninstall, first-run setup) take the sync lock. Password migrations run only under it; --status and --history read without writing.
  • Garmin: a relogin keeps the stored session until the new login succeeds, at most one relogin happens per run, and --update-password checks each new password with a real login before storing it.
  • Eufy: if the original login endpoint looks moved or gone, login retries once at home-api.eufylife.com, the host the current EufyLife app uses. Never on a wrong password or a rate limit.
  • Strava: from 2027-01-04 requests go to api-v3.strava.com, per Strava's changelog, with the current host as a fallback on connection failures.
  • Smaller fixes: the weight-only reach-back window is capped at 14 days; a weight Zwift can't accept is skipped instead of retried; --update refreshes the package index under uv; the end-of-run summary and doctor label the latest weigh-in accurately; headless docs cover setting TZ; pytest and ruff are declared as a dev group.

681 tests pass locally.

Chunked vaults are written under a fresh generation (vault:<gen>:<i>) and the header is switched last, so a save killed at any step leaves the old or the new vault readable. The header carries a checksum, and the next save sweeps leftovers from the neighbouring generations.

A damaged vault (bad JSON, missing or mismatched chunk, corrupt credentials file) now raises VaultCorruptError instead of reading as empty, so the next store can no longer save an empty vault over stored passwords. Released single-entry and vault:<i> layouts are still read and migrate on the next save.
--status and --history now resolve passwords without migrating legacy
keychain items or YAML passwords, and the sync path migrates after it
takes the lock, so an unlocked process never writes the credential vault.
…over

Chunk names now carry a random suffix recorded in the header, a save
re-reads the header before committing and aborts with a retryable error
if another process switched it, and a journal entry lists every tag that
may still hold chunks so later saves and --use-file-store can delete
leftovers from any number of interrupted saves.
A later 401 or 403 in the same run now raises the stored relogin error or
the call's own error instead of running another full login, so a
Cloudflare block on the API no longer costs a login per call.
Every credential change (store, delete, migration, store switches, uninstall
cleanup) now holds an exclusive OS lock on vault.lock in the data dir from
load to sweep. The lock is reentrant within a process, waits up to 30 s, and
refuses to write if it cannot be opened or acquired. With writers serialized,
the journal grace period and the header re-check are gone: any journal tag
the live header does not name is a leftover and is deleted by the next save.

- Uninstall deletes each lock file while still holding it on POSIX, and a
  lock taken on a file that was unlinked meanwhile is retried on the new one.
  Windows keeps release-then-delete.
- A present but wrong-typed passwords/tokens section raises VaultCorruptError
  instead of reading as empty.
- Keychain saves refuse more than MAX_CHUNKS chunks before writing anything,
  so use_keychain_store keeps the file. Released-layout vaults of any size
  still read.
- An existing credentials file that cannot be read or parsed raises during
  backend selection instead of silently falling back to the keychain; doctor
  reports it as a failure.
@sturimcode
sturimcode merged commit cbb000a into main Oct 2, 2026
11 checks passed
@sturimcode
sturimcode deleted the release/1.14.0 branch October 2, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant