8000 Separate R2 buckets and scoped keys: staging images, DB replica · Issue #438 · jra3/mulm · GitHub
[go: up one dir, main page]

Skip to content

Separate R2 buckets and scoped keys: staging images, DB replica #438

Open

Description

@jra3

Staging's CONFIG_JSON is the prod config with only domain, WebAuthn and disableEmails overridden (docs/INFRASTRUCTURE.md). So staging has the prod R2 key, and per docs/INFRASTRUCTURE.md:122 that one key covers both buckets:

  • basny-bap-data: image uploads. Anything uploaded or deleted through staging lands in prod's images.
  • basny-db-replica: the Litestream WAL replica. Staging only avoids writing to it because start.sh skips litestream replicate when STAGING=1. The key itself could write to or delete prod's generation history.

This came up in the #360 review, where public test credentials briefly gave admin access to staging.

Want

  • A staging image bucket (e.g. basny-bap-staging-data); staging's storage config points at it.
  • Split the DB replica credentials from the image credentials:
    • Prod: a Litestream-only key scoped to basny-db-replica, separate from the image-upload key on basny-bap-data. A leaked upload key then can't touch the DB replica.
    • Staging: a read-only token on basny-db-replica, enough for litestream restore on refresh and nothing else. Staging doesn't need its own replica bucket, because it never replicates and refresh has to read prod's generation anyway.
  • Staging's CONFIG_JSON no longer contains any prod read/write key.

Open questions

  • Should staging still show prod images? The refreshed DB references image keys in basny-bap-data. Options: serve them read-only from the prod bucket's public URL, copy the bucket on refresh, or accept broken images on staging.
  • litestream.yml hardcodes the bucket and endpoint, and start.sh derives LITESTREAM_* from CONFIG_JSON.storage.s3*. Give Litestream its own config keys (or separate Fly secrets) so its credential can differ from the upload one, in prod as well as staging.

Activity

  1. added
    securitySecurity improvements and vulnerability fixes
    infrastructureInfrastructure, deployment, and DevOps
    needs-triageMaintainer needs to evaluate this issue
    on Sep 28, 2026
  2. changed the title [-]Staging gets its own R2 bucket and scoped keys[/-] [+]Separate R2 buckets and scoped keys: staging images, DB replica[/+] on Sep 29, 2026
  3. jra3 commented on Oct 9, 2026

    @jra3
    OwnerAuthor

    Decisions from 2026-10-08/09 (these were drafted as #493, now closed as a duplicate):

    • Staging shows prod images by copying them: prod basny-bap-data → the staging bucket, by rclone sync or an SDK copy script.
      • Copy whenever the DB is refreshed (a start.sh restore, or scripts/staging.sh refresh), so staging's DB and images come from the same point in time.
      • Also weekly, as a scheduled refresh followed by a seed, since a refresh wipes the test users.
    • Staging's read-only token therefore covers basny-db-replica and basny-bap-data.
    • When STAGING=1, start.sh uses that read-only pair for Litestream rather than storage.*.
    • Once staging no longer holds prod's key, rotate prod's key.
    • species_images.image_url stores full prod r2.dev URLs. Those keep reading from prod's public URL, which is fine.

    Stopgap already done: #492 stops staging's scheduled jobs. Before it, the R2 orphan sweep ran against prod's bucket using staging's stale DB.

    Still open: rclone in the image (~50MB, prod too) vs a small SDK copy script; bucket size.

  4. jra3 commented on Oct 9, 2026

    @jra3
    OwnerAuthor

    Created 2026-10-09 with wrangler: bucket basny-bap-staging-data, location ENAM like prod, r2.dev public access at https://pub-842096e60f5b4512a0e730e6c0d6ae76.r2.dev. Empty so far. Prod's basny-bap-data is 231 MB / 4,188 objects, so a full copy on every refresh is cheap. Prod has no CORS config (uploads go through the server), so none was added here. Next: two scoped R2 tokens (dashboard), staging secrets, then start.sh and the copy script.

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

    infrastructureInfrastructure, deployment, and DevOpsneeds-triageMaintainer needs to evaluate this issuesecuritySecurity improvements and vulnerability fixes

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0