mixly / DEPLOY.md
hank-vibe's picture
docs: update Space references from solora-hq/Mixly to jay-hank/mixly
2caaa2b
|
Raw
History Blame Contribute Delete
9.58 kB

Mixly on Hugging Face Spaces β€” deploy & operations

The Space (jay-hank/mixly) is a Docker host. Its Dockerfile clones the private GitHub repo phamdung2209/Mixly at build time and runs the Next.js app.

Public URL: https://jay-hank-mixly.hf.space

⚠️ Data isolation. Mixly lives under the jay-hank HF account β€” a different account from solora-hq, which hosts Tierly and is LIVE with real merchant data. Mixly's DATABASE_URL, S3_BUCKET, S3_ENDPOINT, and Space name are a completely separate set of identifiers from Tierly's β€” never copy a value from Tierly's DEPLOY.md here. Concretely: DB path is /data/mixly.db (not tierly.db), bucket is Mixly-storage (not Tierly-storage) under the jay-hank namespace (not solora-hq), Space is jay-hank/mixly (not solora-hq/Tierly). jay-hank has no access to solora-hq at all, so pointing Mixly at any of Tierly's identifiers won't just risk corrupting or leaking live merchant data β€” it will most likely just fail outright.


0. First-time setup (do this once β€” the Space already exists)

The Space jay-hank/mixly has already been created (Docker SDK) on the jay-hank HF account. What's left:

  1. Add the remote and push this repo:
    git remote add origin https://huggingface.co/spaces/jay-hank/mixly
    git push -u origin main
    
    The first build will fail (or crash-loop) until the secrets in Β§2 are set β€” that's expected, HF just needs a repo to attach the build to.
  2. Create the S3 storage bucket: HF β†’ your jay-hank account β†’ Storage β†’ New bucket β†’ name it exactly Mixly-storage (bare name, no slashes). Double check it is not Tierly-storage. Read the risk note below first β€” Storage Buckets may not be available on this account's plan.
  3. Set every secret in Β§2 below (Space β†’ Settings β†’ Variables and secrets), then Space β†’ Settings β†’ Factory rebuild.

🚧 Risk: HF Storage Buckets may not be available on a free, non-Pro account. jay-hank is a free, non-Pro account. This matters more than it sounds: the HF free tier has an ephemeral filesystem, so without a working S3 bucket the SQLite database is destroyed on every rebuild, restart, or wake-from-sleep β€” the app would silently lose every merchant's bundles, settings, and stats. Tierly has already suffered exactly this data-loss failure once, which is why the restore-on-boot mechanism (Β§4) exists. Before treating this deployment as production-ready, confirm that a bucket named Mixly-storage can actually be created under the jay-hank namespace and that S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY work against https://s3.hf.co/jay-hank. If HF storage buckets aren't available on this plan, the options are: enable HF Persistent Storage (paid), upgrade the account, or point the backup at a different S3-compatible provider (src/lib/s3.ts speaks plain S3, so any S3-compatible endpoint should work).


1. GitHub token (required β€” the repo is private)

Cloning a private repo inside the Docker build needs a token. Create a fine-grained Personal Access Token, scoped to just this repo, read-only:

  1. GitHub β†’ Settings β†’ Developer settings β†’ Personal access tokens β†’ Fine-grained tokens β†’ Generate new.
  2. Resource owner: phamdung2209. Repository access: Only select repositories β†’ Mixly.
  3. Permissions β†’ Repository β†’ Contents: Read-only (that's all it needs).
  4. Expiration: 90 days (rotate on expiry). Generate β†’ copy the github_pat_….

Then add it to the Space as a build secret:

  • Space β†’ Settings β†’ Variables and secrets β†’ New secret
  • Name: GITHUB_TOKEN Β· Value: the github_pat_…

The Dockerfile reads it via --mount=type=secret,id=GITHUB_TOKEN, so it is never baked into an image layer.


2. Space secrets (runtime) β€” Settings β†’ Variables and secrets

πŸ”’ Paste values in the HF UI, never into code/git. Secrets (hidden) vs Variables (visible) β€” put all credentials as Secrets.

Required

Name Value / where from
GITHUB_TOKEN build secret from Β§1
SHOPIFY_API_KEY Partner Dashboard β†’ Mixly β†’ API credentials
SHOPIFY_API_SECRET β€³ (secret)
NEXT_PUBLIC_SHOPIFY_API_KEY same value as SHOPIFY_API_KEY
SHOPIFY_SCOPES read_orders,read_products,write_discounts (match shopify.app.toml)
SHOPIFY_APP_URL https://jay-hank-mixly.hf.space
SHOPIFY_API_VERSION 2026-07
DATABASE_URL file:/data/mixly.db (persistent mount β€” see Β§3)
SESSION_ENCRYPTION_KEY openssl rand -hex 32. Set once, never change (rotating it forces every store to re-auth).
HF_SPACE_ID jay-hank/mixly β€” the in-code default (src/lib/ops/backup.ts) is still solora-hq/Mixly, which is wrong for this deployment, so this must be set explicitly.

Ops Console + DB access (/ops, /ops/studio)

Name Value
OPS_ALLOWLIST comma-separated emails allowed into /ops
AUTH_SECRET npx auth secret. Keep stable (else your /ops sessions drop).
AUTH_GOOGLE_ID Google Cloud Console β†’ OAuth client ID
AUTH_GOOGLE_SECRET β€³ (redirect URI: https://jay-hank-mixly.hf.space/api/auth/callback/google)

DB backup (Β§4) β€” HF Storage Bucket via S3 API

Name Value
S3_ENDPOINT https://s3.hf.co/jay-hank (gateway scoped to the namespace)
S3_BUCKET Mixly-storage (bare bucket name β€” not Tierly-storage)
S3_ACCESS_KEY_ID HFAK… β€” HF Access Tokens β†’ token (Write) β†’ dropdown β†’ Generate S3 credentials
S3_SECRET_ACCESS_KEY β€³ (shown once)
HF_TOKEN HF bearer token β€” used to rebuild/restart the Space from /ops/backup after a DB restore (Β§4), and by scripts/sync-env.mjs (Β§5), NOT the S3 creds

Optional (features degrade gracefully when unset)

SENTRY_DSN, GITHUB_REPO (overrides the clone source; the Dockerfile's ARG GITHUB_REPO build arg already defaults to the right repo).

Mixly has no Crisp chat, QStash, or email (Resend) integration β€” unlike Tierly, none of NEXT_PUBLIC_CRISP_WEBSITE_ID, QSTASH_*, RESEND_API_KEY, EMAIL_FROM, or CRON_SECRET apply here; don't set them.


3. Persistent storage

Space β†’ Settings β†’ Persistent storage β†’ enable (Small is plenty). It mounts at /data, which is where DATABASE_URL=file:/data/mixly.db points. Without it, /data resets on every rebuild β€” which is exactly why Β§4 exists.

Do not mount the Storage Bucket as a data volume. Backup talks to it over the S3 API (Β§4); a bucket mount is a schedule-time dependency and, if HF can't attach it, the Space fails with "Scheduling failure: unable to schedule".


4. Database backup / restore (HF Storage Bucket, S3 API)

The app backs the SQLite DB up to a Storage Bucket over the S3 API and restores it on boot if /data is empty β€” so data survives a rebuild even on the free tier. Network-only (no mount), so it never blocks Space scheduling.

  • What's backed up: only the SQLite DB file (/data/mixly.db). It holds everything stateful: Shopify sessions (tokens encrypted at rest), bundle offers, bundle configs, shop profiles, settings. Migrations live in git; nothing else needs backing up.
  • The one thing that is NOT in the bucket: SESSION_ENCRYPTION_KEY. It lives only in Space secrets. Keep it β€” a restored DB with a lost/rotated key can't decrypt stored Shopify tokens (the app then just re-exchanges them, no data loss, but avoid churn).
  • Bucket: jay-hank/Mixly-storage. Objects: latest.db (overwritten in place β€” no history growth) + snapshots/mixly-YYYY-MM-DD.db (daily, pruned after 30 days).
  • Schedule: in-process β€” an initial backup ~1 min after boot, then hourly, plus a debounced write after each DB change and a final flush on SIGTERM (see src/lib/backup.ts + src/lib/s3.ts, started from src/instrumentation.ts).
  • Operator-driven backup/restore: unlike a plain snapshot loop, Mixly ships an /ops/backup panel (Google-authenticated, see Β§2) that can trigger an immediate backup, upload a snapshot to restore from, and inspect it (table/row checks) before committing. Restoring requires typing overwrite to confirm, snapshots the current DB as a pre-restore-…db rollback key first, then calls restartSpace() (needs HF_TOKEN) to restart the Space onto the restored file.

5. Deploy / rebuild

  • Redeploy the web app: push to GitHub main, then in the Space use Settings β†’ Factory rebuild (re-clones latest main).
  • Push local env vars as Space secrets: HF_TOKEN=… node scripts/sync-env.mjs .env.production.local jay-hank/mixly β€” reads KEY=value lines and pushes each as a secret via the HF API. The script's built-in default Space is still solora-hq/Mixly (wrong for this deployment), so always pass jay-hank/mixly explicitly as the second arg. Changes apply on the next rebuild/restart.
  • Deploy the Shopify Function / extensions: that is a separate target, run from your machine (needs the Shopify CLI + Partner auth) β€” npm run app:deploy. The container also runs this non-fatally during a Space build (see Dockerfile), gated on the SHOPIFY_CLI_PARTNERS_TOKEN build secret.

After changing scopes or the app URL, re-open the app in the store to re-consent.