Self-hosting

The MVP deployment uses Vercel for Next.js, a Cloudflare Worker with KV for generated image caching and private optional user backgrounds, Analytics Engine for anonymous run counters, and Turnstile for upload verification. Built-in wallpapers can run without the Worker, but custom uploads and run counters cannot.

Requirements

  • Node.js 24.x and npm
  • a Vercel account
  • a Cloudflare account with Workers, KV, Analytics Engine, and Turnstile
  • optional Razorpay and Resend accounts for project support
  • Git

No application database or content-provider key is required for the MVP. KV holds active-day lesson and cache records, normalized custom images, and minimal custom-upload lifecycle metadata.

Local development

npm.cmd install
Copy-Item .env.example .env.local
npm.cmd run dev

Open http://localhost:3000. Without cache environment variables, the wallpaper route generates and returns images directly.

Environment contract

VariableRequiredPurpose
NEXT_PUBLIC_SITE_URLproductionCanonical public origin
NEXT_PUBLIC_SHORTCUT_URLnoPublished universal iCloud Shortcut
NEXT_PUBLIC_CF_ANALYTICS_TOKENnoCookie-free aggregate web analytics
NEXT_PUBLIC_TURNSTILE_SITE_KEYfor uploadsPublic Turnstile widget key
CACHE_WORKER_URLfor cacheDeployed Worker origin
CACHE_WORKER_SECRETfor cachePrivate lookup and upload HMAC
CACHE_SIGNING_SECRETfor cachePublic asset URL signature
TURNSTILE_SECRETfor uploadsServer-side Turnstile verification secret
TURNSTILE_HOSTNAMESfor uploadsExact comma-separated production hostnames
CRON_SECRETfor cronProtects the rollover route
SUPPORT_URLfor supportPublished https://rzp.io/rzp/... Payment Page (legacy /l/... URLs also work)
RAZORPAY_WEBHOOK_SECRETfor support emailHMAC secret configured in Razorpay
RESEND_API_KEYfor support emailRestricted transactional sending key
SUPPORT_EMAIL_FROMfor support emailVerified WallCab sender identity

Generate long, independent random values for each secret. Never prefix a secret with NEXT_PUBLIC_.

Cloudflare Worker and KV

  1. Create a KV namespace.
  2. Replace the placeholder namespace ID in worker/wrangler.jsonc.
  3. Add CACHE_WORKER_SECRET and CACHE_SIGNING_SECRET with wrangler secret put.
  4. Run npm.cmd run worker:deploy.
  5. Save the returned Worker URL for Vercel.

The Worker defaults to workers.dev. Restrict or move it behind a custom route if your deployment policy requires it. No R2 subscription or database is needed. On the Workers Free plan, KV operations fail after a free allowance is exhausted rather than becoming usage charges.

The configured wallcab_usage Analytics Engine dataset is created automatically when the first accepted run event is written. The public deployment has a write-only binding and does not need a Cloudflare API token. Keep any Account Analytics Read token exclusively in a separate private reporting application.

Create a managed Turnstile widget restricted to the real WallCab hostnames. The client action is custom_background_upload; the server rejects another action or hostname. Use Cloudflare's documented test keys only on localhost.

Vercel

Import the GitHub repository, keep the framework preset on Next.js, select Node.js 24.x, and add the environment values above. vercel.json schedules one rollover call at 23:00 UTC. The cron is an optimization; lazy provider fetching and rendering remain the fallback.

Promote a preview only after exercising the configurator, custom upload and deletion, API redirect, source credits, and Shortcut flow.

Optional project support

Create a live Razorpay Payment Page in INR with Customer Decides Amount, email and phone only, no expiry, and a post-payment redirect to WallCab. Use a dedicated Razorpay merchant account for WallCab so its account-wide payment.captured webhook cannot include unrelated payments.

Verify wallcab.dhruvdev.me as a sending domain in Resend and create a send-only API key. Configure the production webhook URL as https://your-domain.example/api/webhooks/razorpay, select only payment.captured, and use a unique secret of at least 32 characters. The endpoint accepts only HMAC-signed domestic INR captures with a valid Razorpay event envelope. Resend receives the supporter email and an optional bounded name only to send the transactional thank-you; WallCab does not persist them.

Provider diagnostics

Every wallpaper response includes X-WallCab-Content-Mode and X-WallCab-Content-Provider. The configurator reads /api/wallpaper/status to show the same result beside its live preview.

Vercel Runtime Logs receive structured wallcab.content and wallcab.wallpaper events with the UTC date, resolved category, provider, and fallback reason. They contain no IP address or user preference identifier.

Valid wallpaper GET requests also emit one anonymous Analytics Engine event after the response. Preview, status, HEAD, invalid, rate-limited, cron, and internal cache requests are not counted. The event includes only operational enums and a new random request ID; it excludes complete URLs, notes, custom background IDs, IP addresses, user agents, and persistent client identifiers.

For an authenticated snapshot of every category already prepared or lazily generated today:

Invoke-RestMethod `
  -Uri "https://your-domain.example/api/internal/status" `
  -Headers @{ Authorization = "Bearer $env:CRON_SECRET" }

The status route is read-only and does not call an external content provider. A category appears as not_generated until the rollover manifest or a wallpaper request has prepared it.

Docker status

Docker is intentionally deferred from the MVP. A container image needs a clear persistent-cache story, health checks, and documented Sharp native dependencies before it becomes a supported deployment route. It appears on the public roadmap, but no placeholder Dockerfile is shipped today.