From fe13f3b283c8d10b752035614ebb6d0573a4eca8 Mon Sep 17 00:00:00 2001 From: Padreug Date: Wed, 10 Jun 2026 17:04:05 +0200 Subject: [PATCH] refactor: delegate webapp build to flake.lib.mkWebapp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops the local stdenv.mkDerivation + fetchPnpmDeps in favor of calling the webapp flake's own lib.mkWebapp (added in aiolabs/webapp#97). The hub build now runs through the same builder as the per-standalone builds in server-deploy, which means ONE pnpmDepsHash lives in webapp's flake.nix and no other repo carries one. Options changed: - ADDED flake (required): the webapp flake input. Server-deploy hosts pin this to inputs.webapp (main) or inputs.webapp-dev (staging channel). - ADDED brandDir: path to the brand kit. Defaults to the flake's committed aiolabs default. Override for per-host branding (logo, manifest name, theme colors). - REMOVED src: replaced by flake (which carries the source). - REMOVED gitUrl, gitRef: replaced by flake input ref selection. - REMOVED pnpmDepsHash: managed inside webapp's flake.nix now. - REMOVED appName: per #99/#100's strict policy, VITE_APP_NAME is brand-controlled. Custom names flow through brandDir + brand.json, not env vars. Hosts that today set appName must either accept the default brand's name or supply a per-host brandDir. Other options (nostrRelays, lnbitsBaseUrl, hubXUrl, …) flow into the build via lib.mkWebapp's extraEnv. nginx root moves from \$out/share/webapp to \$out/dist (lib.mkWebapp's output layout). Co-Authored-By: Claude Opus 4.7 (1M context) --- webapp.nix | 213 ++++++++++++++++++----------------------------------- 1 file changed, 70 insertions(+), 143 deletions(-) diff --git a/webapp.nix b/webapp.nix index e6b8c90..177ec21 100644 --- a/webapp.nix +++ b/webapp.nix @@ -1,134 +1,81 @@ -# NixOS module for building and serving the Vue 3 webapp +# NixOS module for serving the Vue 3 webapp. +# +# Build is delegated to the webapp flake's lib.mkWebapp (since +# aiolabs/webapp#97). This module owns: +# - the nginx vhost +# - the per-deployment VITE_* env vars (relays, LNbits URL, hub +# tile URLs, …) that get baked into the bundle +# - brand kit selection via `brandDir` +# +# Per-deployment name customization flows through brand.json under +# `brandDir`, NOT through a `VITE_APP_NAME` knob — see strict policy +# landed in aiolabs/webapp#99 / #100. { config, lib, pkgs, ... }: let cfg = config.services.webapp; - # Build the Vue 3 webapp using stdenv + pnpm.configHook - webapp = pkgs.stdenv.mkDerivation (finalAttrs: { - pname = "aio-webapp"; - version = "1.0.0"; + webapp = cfg.flake.lib.mkWebapp { + inherit pkgs; + brandDir = cfg.brandDir; + extraEnv = { + ELECTRON_SKIP_BINARY_DOWNLOAD = "1"; + NODE_ENV = "production"; - # Use src option if provided (flake input), otherwise fetch from git - src = if cfg.src != null then cfg.src else builtins.fetchGit { - url = cfg.gitUrl; - ref = cfg.gitRef; + VITE_NOSTR_RELAYS = builtins.toJSON cfg.nostrRelays; + VITE_ADMIN_PUBKEYS = builtins.toJSON cfg.adminPubkeys; + + VITE_LNBITS_BASE_URL = cfg.lnbitsBaseUrl; + VITE_LNBITS_DEBUG = if cfg.lnbitsDebug then "true" else "false"; + VITE_LNBITS_NOSTR_TRANSPORT_PUBKEY = cfg.lnbitsNostrTransportPubkey; + VITE_WEBSOCKET_ENABLED = if cfg.websocketEnabled then "true" else "false"; + + VITE_LIGHTNING_DOMAIN = cfg.lightningDomain; + + VITE_VAPID_PUBLIC_KEY = cfg.vapidPublicKey; + VITE_PUSH_NOTIFICATIONS_ENABLED = if cfg.pushNotificationsEnabled then "true" else "false"; + + VITE_PICTRS_BASE_URL = cfg.pictrsBaseUrl; + VITE_MARKET_NADDR = cfg.marketNaddr; + VITE_DEMO_MODE = if cfg.demoMode then "true" else "false"; + + VITE_HUB_EVENTS_URL = cfg.hubEventsUrl; + VITE_HUB_LIBRA_URL = cfg.hubLibraUrl; + VITE_HUB_WALLET_URL = cfg.hubWalletUrl; + VITE_HUB_CHAT_URL = cfg.hubChatUrl; + VITE_HUB_FORUM_URL = cfg.hubForumUrl; + VITE_HUB_MARKET_URL = cfg.hubMarketUrl; + VITE_HUB_TASKS_URL = cfg.hubTasksUrl; + VITE_HUB_RESTAURANT_URL = cfg.hubRestaurantUrl; }; - - # Fixed-output derivation of the pnpm offline store. - # On first build / lockfile change, set hash = lib.fakeHash and rebuild; - # Nix will report the correct hash to substitute. - pnpmDeps = pkgs.fetchPnpmDeps { - inherit (finalAttrs) pname version src; - hash = cfg.pnpmDepsHash; - fetcherVersion = 3; - }; - - nativeBuildInputs = [ pkgs.nodejs_20 pkgs.pnpm pkgs.pnpmConfigHook ]; - - # Skip Electron binary download (we're building a web app, not desktop) - ELECTRON_SKIP_BINARY_DOWNLOAD = "1"; - - # Environment variables for Vite build (VITE_* are embedded at build time) - # App Configuration - VITE_APP_NAME = cfg.appName; - - # Nostr Configuration - VITE_NOSTR_RELAYS = builtins.toJSON cfg.nostrRelays; - VITE_ADMIN_PUBKEYS = builtins.toJSON cfg.adminPubkeys; - - # API Configuration - VITE_LNBITS_BASE_URL = cfg.lnbitsBaseUrl; - VITE_LNBITS_DEBUG = if cfg.lnbitsDebug then "true" else "false"; - VITE_WEBSOCKET_ENABLED = if cfg.websocketEnabled then "true" else "false"; - VITE_LNBITS_NOSTR_TRANSPORT_PUBKEY = cfg.lnbitsNostrTransportPubkey; - - # Lightning Address Domain - VITE_LIGHTNING_DOMAIN = cfg.lightningDomain; - - # Push Notifications - VITE_VAPID_PUBLIC_KEY = cfg.vapidPublicKey; - VITE_PUSH_NOTIFICATIONS_ENABLED = if cfg.pushNotificationsEnabled then "true" else "false"; - - # Image Upload Configuration (pict-rs) - VITE_PICTRS_BASE_URL = cfg.pictrsBaseUrl; - - # Market Configuration - VITE_MARKET_NADDR = cfg.marketNaddr; - - # Demo Mode - VITE_DEMO_MODE = if cfg.demoMode then "true" else "false"; - - # Hub → standalone navigation URLs. - # The chakra tiles in src/pages/Hub.vue build from these. With - # path-mode deployment, set these to canonical trailing-slash URLs: - # hubMarketUrl = "https://demo.example.com/market/"; - # With subdomain deployment: - # hubMarketUrl = "https://market.example.com"; - # When unset (empty string), the corresponding chakra renders ghosted - # (non-link) — useful for hosts that don't deploy a given module. - VITE_HUB_EVENTS_URL = cfg.hubEventsUrl; - VITE_HUB_LIBRA_URL = cfg.hubLibraUrl; - VITE_HUB_WALLET_URL = cfg.hubWalletUrl; - VITE_HUB_CHAT_URL = cfg.hubChatUrl; - VITE_HUB_FORUM_URL = cfg.hubForumUrl; - VITE_HUB_MARKET_URL = cfg.hubMarketUrl; - VITE_HUB_TASKS_URL = cfg.hubTasksUrl; - VITE_HUB_RESTAURANT_URL = cfg.hubRestaurantUrl; - - # Additional env vars - NODE_ENV = "production"; - - # pnpm.configHook sets up the offline store and runs - # `pnpm install --offline --frozen-lockfile` before buildPhase. - buildPhase = '' - runHook preBuild - pnpm run build - runHook postBuild - ''; - - # Install the built static files (Vite outputs to ./dist) - installPhase = '' - runHook preInstall - mkdir -p $out/share/webapp - cp -r dist/* $out/share/webapp/ - runHook postInstall - ''; - - # Don't run pnpm test - doCheck = false; - - meta = with lib; { - description = "AIO Community Hub - Vue 3 webapp"; - license = licenses.mit; - platforms = platforms.linux; - }; - }); + }; in { options.services.webapp = { enable = lib.mkEnableOption "AIO webapp"; - src = lib.mkOption { - type = lib.types.nullOr lib.types.path; - default = null; + flake = lib.mkOption { + type = lib.types.attrs; + example = lib.literalExpression "inputs.webapp"; description = '' - Source tree for the webapp. When set, this is used directly instead of - fetching via builtins.fetchGit. Use this with flake inputs for pure evaluation. + The webapp flake (as a flake input, not raw source). The module + calls `flake.lib.mkWebapp` to build. Pin per-host to pick the + right channel — `inputs.webapp` for main, `inputs.webapp-dev` + for the dev/staging channel. ''; }; - gitUrl = lib.mkOption { - type = lib.types.str; - default = "https://git.atitlan.io/aiolabs/webapp"; - description = "Git repository URL for the webapp source"; - }; - - gitRef = lib.mkOption { - type = lib.types.str; - default = "main"; - example = "demo"; - description = "Git branch or tag to build from"; + brandDir = lib.mkOption { + type = lib.types.path; + default = "${cfg.flake}/branding/default"; + defaultText = lib.literalExpression ''"\${cfg.flake}/branding/default"''; + description = '' + Path to the brand kit directory consumed by the build. Defaults + to the webapp flake's committed aiolabs brand. Override to a + per-host directory in server-deploy (e.g. + `./../branding/cfaun`) for per-host logo + manifest name + + theme colors. See `branding/README.md` in the webapp repo. + ''; }; domain = lib.mkOption { @@ -137,23 +84,6 @@ in { description = "Domain name for the webapp"; }; - pnpmDepsHash = lib.mkOption { - type = lib.types.str; - default = lib.fakeHash; - description = '' - SRI hash of pnpm dependencies fetched from `pnpm-lock.yaml`. - Initially set to `lib.fakeHash`; on first build with a new lockfile, - Nix will fail with the correct hash to substitute in. - ''; - }; - - # App Configuration - appName = lib.mkOption { - type = lib.types.str; - default = "MyApp"; - description = "Application name displayed in the UI"; - }; - # Nostr Configuration nostrRelays = lib.mkOption { type = lib.types.listOf lib.types.str; @@ -308,24 +238,21 @@ in { }; config = lib.mkIf cfg.enable { - # Nginx virtualHost for webapp (global nginx settings are in config/nginx.nix) + # lib.mkWebapp's output layout is `$out/dist/`, not `$out/share/webapp/`, + # so the vhost root points directly at dist/. services.nginx.virtualHosts.${cfg.domain} = { - # SSL configuration forceSSL = cfg.enableSSL; enableACME = cfg.enableSSL; - # Serve the built webapp - root = "${webapp}/share/webapp"; + root = "${webapp}/dist"; locations = { "/" = { - # Try files, fallback to index.html for SPA routing. - # Everything matching this prefix ultimately serves the SPA - # shell (index.html), which must revalidate on every load so - # new deploys are picked up without the browser holding a - # stale shell pointing at deleted hashed assets. The more- - # specific regex location below overrides this for hashed - # static assets, which stay immutable. + # SPA shell must revalidate on every load so new deploys are + # picked up without the browser holding a stale shell pointing + # at deleted hashed assets. The more-specific regex location + # below overrides this for hashed static assets, which stay + # immutable. tryFiles = "$uri $uri/ /index.html"; extraConfig = '' add_header Cache-Control "no-cache";