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
| Variable | Required | Purpose |
|---|---|---|
NEXT_PUBLIC_SITE_URL | production | Canonical public origin |
NEXT_PUBLIC_SHORTCUT_URL | no | Published universal iCloud Shortcut |
NEXT_PUBLIC_CF_ANALYTICS_TOKEN | no | Cookie-free aggregate web analytics |
NEXT_PUBLIC_TURNSTILE_SITE_KEY | for uploads | Public Turnstile widget key |
CACHE_WORKER_URL | for cache | Deployed Worker origin |
CACHE_WORKER_SECRET | for cache | Private lookup and upload HMAC |
CACHE_SIGNING_SECRET | for cache | Public asset URL signature |
TURNSTILE_SECRET | for uploads | Server-side Turnstile verification secret |
TURNSTILE_HOSTNAMES | for uploads | Exact comma-separated production hostnames |
CRON_SECRET | for cron | Protects the rollover route |
SUPPORT_URL | for support | Published https://rzp.io/rzp/... Payment Page (legacy /l/... URLs also work) |
RAZORPAY_WEBHOOK_SECRET | for support email | HMAC secret configured in Razorpay |
RESEND_API_KEY | for support email | Restricted transactional sending key |
SUPPORT_EMAIL_FROM | for support email | Verified WallCab sender identity |
Generate long, independent random values for each secret. Never prefix a secret with NEXT_PUBLIC_.
Cloudflare Worker and KV
- Create a KV namespace.
- Replace the placeholder namespace ID in
worker/wrangler.jsonc. - Add
CACHE_WORKER_SECRETandCACHE_SIGNING_SECRETwithwrangler secret put. - Run
npm.cmd run worker:deploy. - 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.