Self-hosting

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

Requirements

  • Node.js 24.x and npm
  • a Vercel account
  • a Cloudflare account with Workers, KV, and Turnstile
  • 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

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.

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.

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.

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.