Skip to content

Let an operator name a backup they create #431

Description

@DutchJaFO

Background

POST /api/v1/admin/backups/create (#349) takes no parameters and names the file it writes with the
application's own convention. A backup an operator asks for is theirs, and they should be able to say what
it is called: "before the 1.9 upgrade" is a name that still means something in six months, where
quotinatordata_v26.9_20261001T084512123Z.db requires reading a timestamp back into a memory.

The naming convention exists so that a backup the application took for its own protection carries the
schema state it holds. That reason does not apply to a file the operator requested and will recognise by
their own label (developer, 2026-10-01): the convention governs automatic backups only.

What needs to be done

  1. POST /api/v1/admin/backups/create accepts an optional caller-supplied name. Omitted, the
    behaviour is exactly as today: the application's own convention. Supplied, it is what the file is
    called. The response reports the stored name either way, so the caller never has to predict it.

  2. The name is sanitised server-side, never taken from the client as-is. No path separators, no
    traversal segments, and the resolved path verified to sit inside BackupsPath. This is the same
    guarantee Upload a backup file #353 requirement 5 states for its upload, and it must be the same code, not a second
    implementation: BackupFileNames already exists as the one place a caller-supplied backup identifier
    is judged, written that way deliberately because "a path-traversal check implemented twice is a
    path-traversal check that will eventually differ in one of the two places, and only one of them will be
    the one an attacker finds."
    Whichever of this issue and Upload a backup file #353 lands first puts the sanitiser there, and
    the second uses it.

  3. A collision is refused, not resolved. A name that already exists returns 409 naming the existing
    file and writes nothing, per Upload a backup file #353 requirement 6: auto-renaming hands back a file under a name the
    operator did not choose, which is worse than being told.

  4. Uniqueness is decided case-insensitively, per Upload a backup file #353 requirement 7 and CLAUDE.md's
    case-insensitive-by-default rule, so whether a create destroys an existing restore point does not
    depend on whether the host filesystem is Windows or Linux.

  5. An operator-named file stays listable, downloadable, restorable and removable. It appears in
    GET /backups like any other, and BackupFileInfo.TakenAtUtc already reads the write time from the
    filesystem rather than parsing the name, so nothing needs to change for a name that carries no
    timestamp. Confirm this rather than assume it: the point of the requirement is that the rest of the
    backup surface does not quietly depend on the convention.

  6. The extension is the application's to decide, not the caller's. A stored backup is a SQLite
    database file and is named .db whatever the caller passed, so a name cannot be used to make a file
    look like something it is not.

  7. docs/api-endpoints.md and the endpoint's [Description] attributes are updated in the same
    commit
    , per CLAUDE.md's "Keeping API documentation in sync". A new parameter on an existing
    endpoint is exactly the change that rule governs.

Why this is not folded into the filename-version issue

#430 is a defect: the automatic convention is inconsistent across call sites and its single version
number cannot identify the schema state. This is new capability on an endpoint. They meet at exactly one
point, which the other issue states: the convention becomes the default name here, used when no name is
given.

Expected tests

Test class / document Test method / what it verifies Starts
AdminBackupEndpointsTests CreateBackup_WithNoName_UsesTheApplicationsOwnConvention ❌
AdminBackupEndpointsTests CreateBackup_WithAName_StoresTheFileUnderThatName ❌
AdminBackupEndpointsTests CreateBackup_ReportsTheStoredName ❌
AdminBackupEndpointsTests CreateBackup_WhenTheNameAlreadyExists_Answers409 ❌
AdminBackupEndpointsTests CreateBackup_WhenTheNameAlreadyExists_WritesNothing ❌
AdminBackupEndpointsTests CreateBackup_WhenTheNameDiffersOnlyInCase_Answers409 ❌
AdminBackupEndpointsTests CreateBackup_WithAnExtensionOtherThanDb_StoresItAsDb ❌
BackupFileNamesTests Sanitise_RemovesPathSeparatorsAndTraversalSegments ❌
BackupFileNamesTests Sanitise_ResolvesInsideTheBackupsFolder ❌
DatabaseBackupReaderTests GetBackups_IncludesAnOperatorNamedFile ❌
DatabaseBackupReaderTests GetBackups_ReadsTheWriteTimeOfANameCarryingNoTimestamp ❌

The last two are requirement 5's point: they fail if any part of the backup surface turns out to depend on
the convention it is documented not to depend on.

A T2 document is deliberately not listed. Every requirement here is reachable from the endpoint and the
filesystem in-process, and the index's rule is that a live document earns its place by establishing
something no unit test can. If the plan finds such a thing, it adds one there rather than here.

Definition of done

  • All expected tests listed above start red before implementation
  • All requirements implemented
  • All expected tests pass (green)
  • No regression in related tests
  • Findings summarised in a closing comment

Open for the plan to settle

  • Whether the name arrives as a query parameter or a small request body. The endpoint takes neither today,
    and Upload a backup file #353 deliberately chose its own route rather than overloading POST /backups, so there is a stated
    precedent to read before picking.
  • What a name consisting only of characters the sanitiser strips should do: refuse as invalid input, or
    fall back to the default name. Refusing looks right, since silently substituting a different name is the
    same defect requirement 3 rejects, but it is a decision rather than an obvious answer.

Activity

  1. added this to the Notification system milestone on Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions