How LunaTV Manages Environment Variables: Runtime Configuration Architecture

LunaTV centralizes all application configuration through environment variables read via process.env, separating client-exposed settings (prefixed with NEXT_PUBLIC_) from server-only secrets, and caches resolved configurations in memory through the getConfig() routine.

LunaTV, an open-source media platform developed by MoonTechLab, relies entirely on environment variables for runtime configuration. The codebase implements a centralized configuration pattern that reads variables at startup, applies sensible defaults, and caches the result to optimize performance. This architecture ensures sensitive credentials remain server-side while allowing UI settings to reach the client bundle safely.

Centralized Configuration Through src/lib/config.ts

The heart of LunaTV's environment variable management resides in src/lib/config.ts. The getConfig() function constructs an AdminConfig object by reading process.env values and falling back to hardcoded defaults when variables are absent.

The implementation uses a cachedConfig variable to store the resolved configuration after the first call, preventing repeated environment lookups on subsequent requests. According to the MoonTechLab/LunaTV source code, this lazy-loading pattern appears at lines 94-106 of src/lib/config.ts.

// src/lib/config.ts – site configuration defaults
export async function getConfig(): Promise<AdminConfig> {
  const adminConfig: AdminConfig = {
    SiteConfig: {
      SiteName: process.env.NEXT_PUBLIC_SITE_NAME || 'MoonTV',
      Announcement: process.env.ANNOUNCEMENT || '…',
      SearchDownstreamMaxPage: Number(process.env.NEXT_PUBLIC_SEARCH_MAX_PAGE) || 5,
      // …
    },
    // …
  };
  // …
}

Within the SiteConfig object, specific fields like SiteName default to 'MoonTV' when NEXT_PUBLIC_SITE_NAME is undefined, while SearchDownstreamMaxPage converts the NEXT_PUBLIC_SEARCH_MAX_PAGE string to a number with a fallback of 5.

Authentication and Storage Controls in Middleware

The src/middleware.ts file handles request authentication by evaluating process.env.PASSWORD and process.env.NEXT_PUBLIC_STORAGE_TYPE. When PASSWORD is unset, the middleware redirects all requests to a warning page.

// src/middleware.ts – auth & storage handling
export async function middleware(request: NextRequest) {
  const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage';
  if (!process.env.PASSWORD) {
    return NextResponse.redirect(new URL('/warning', request.url));
  }
  // localstorage mode validates password directly
  if (storageType === 'localstorage') {
    if (authInfo.password !== process.env.PASSWORD) {
      return handleAuthFailure(request, pathname);
    }
    return NextResponse.next();
  }
  // other modes validate signed tokens using the PASSWORD secret
}

For localstorage mode, the middleware validates credentials directly against process.env.PASSWORD. For other storage backends, the same variable serves as the signing secret for token validation. The NEXT_PUBLIC_STORAGE_TYPE variable determines which authentication flow executes, demonstrating how LunaTV uses environment variables to alter runtime behavior.

Third-Party Service Connection Strings

External database and API connections rely on environment variables defined in specific database adapter files. The src/lib/redis.db.ts module instantiates a Redis client using process.env.REDIS_URL, while src/lib/upstash.db.ts reads UPSTASH_URL and UPSTASH_TOKEN for serverless Redis connectivity.

// src/lib/redis.db.ts – connecting to Redis via env var
export const redis = new Redis({
  url: process.env.REDIS_URL!,
});

Similarly, src/lib/kvrocks.db.ts consumes KVROCKS_URL, and Douban proxy configuration uses NEXT_PUBLIC_DOUBAN_PROXY. These variables remain server-only unless explicitly prefixed for client exposure.

Client-Side vs Server-Side Variable Separation

LunaTV follows Next.js conventions by exposing environment variables to the browser only when prefixed with NEXT_PUBLIC_. Variables like NEXT_PUBLIC_SITE_NAME, NEXT_PUBLIC_STORAGE_TYPE, and NEXT_PUBLIC_DOUBAN_PROXY ship to the client bundle, while PASSWORD, REDIS_URL, and USERNAME remain server-side.

The USERNAME environment variable deserves special mention. It defines the owner identity injected into the initial admin user list and powers permission checks throughout the application, including the configSelfCheck function in src/lib/config.ts (lines 36-44).

Summary

  • LunaTV reads all configuration via process.env in src/lib/config.ts
  • The getConfig() routine caches resolved settings in cachedConfig to optimize performance
  • NEXT_PUBLIC_ prefix determines client-side exposure versus server-only access
  • Authentication logic in src/middleware.ts depends on PASSWORD and NEXT_PUBLIC_STORAGE_TYPE
  • Database adapters in src/lib/*.db.ts files consume service-specific connection URLs

Frequently Asked Questions

How does LunaTV handle missing environment variables?

When an environment variable is undefined, LunaTV applies hardcoded defaults within the getConfig() function. For example, process.env.NEXT_PUBLIC_SITE_NAME falls back to 'MoonTV', and NEXT_PUBLIC_SEARCH_MAX_PAGE defaults to 5.

What distinguishes NEXT_PUBLIC_ variables from server-only variables in LunaTV?

Variables prefixed with NEXT_PUBLIC_ are embedded into the client-side JavaScript bundle at build time, making them accessible in browser code. Server-only variables like PASSWORD and REDIS_URL never reach the client and remain accessible only in Node.js runtime contexts such as API routes and middleware.

How does LunaTV optimize environment variable reads?

The getConfig() function in src/lib/config.ts implements a lazy-caching mechanism using a cachedConfig variable. After the first invocation resolves all environment variables and defaults, subsequent calls return the cached object without re-reading process.env.

Which environment variables are required for LunaTV authentication?

The PASSWORD environment variable is mandatory; if absent, the middleware redirects all requests to a warning page. The NEXT_PUBLIC_STORAGE_TYPE variable is also critical as it determines whether the application uses localstorage, Upstash, Redis, or KVROCKS backends.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →