refactor: delegate webapp build to flake.lib.mkWebapp
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) <noreply@anthropic.com>
This commit is contained in:
parent
9d82016399
commit
fe13f3b283
1 changed files with 71 additions and 144 deletions
213
webapp.nix
213
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, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.services.webapp;
|
cfg = config.services.webapp;
|
||||||
|
|
||||||
# Build the Vue 3 webapp using stdenv + pnpm.configHook
|
webapp = cfg.flake.lib.mkWebapp {
|
||||||
webapp = pkgs.stdenv.mkDerivation (finalAttrs: {
|
inherit pkgs;
|
||||||
pname = "aio-webapp";
|
brandDir = cfg.brandDir;
|
||||||
version = "1.0.0";
|
extraEnv = {
|
||||||
|
ELECTRON_SKIP_BINARY_DOWNLOAD = "1";
|
||||||
|
NODE_ENV = "production";
|
||||||
|
|
||||||
# Use src option if provided (flake input), otherwise fetch from git
|
VITE_NOSTR_RELAYS = builtins.toJSON cfg.nostrRelays;
|
||||||
src = if cfg.src != null then cfg.src else builtins.fetchGit {
|
VITE_ADMIN_PUBKEYS = builtins.toJSON cfg.adminPubkeys;
|
||||||
url = cfg.gitUrl;
|
|
||||||
ref = cfg.gitRef;
|
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 <a href> 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 {
|
in {
|
||||||
options.services.webapp = {
|
options.services.webapp = {
|
||||||
enable = lib.mkEnableOption "AIO webapp";
|
enable = lib.mkEnableOption "AIO webapp";
|
||||||
|
|
||||||
src = lib.mkOption {
|
flake = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.path;
|
type = lib.types.attrs;
|
||||||
default = null;
|
example = lib.literalExpression "inputs.webapp";
|
||||||
description = ''
|
description = ''
|
||||||
Source tree for the webapp. When set, this is used directly instead of
|
The webapp flake (as a flake input, not raw source). The module
|
||||||
fetching via builtins.fetchGit. Use this with flake inputs for pure evaluation.
|
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 {
|
brandDir = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.path;
|
||||||
default = "https://git.atitlan.io/aiolabs/webapp";
|
default = "${cfg.flake}/branding/default";
|
||||||
description = "Git repository URL for the webapp source";
|
defaultText = lib.literalExpression ''"\${cfg.flake}/branding/default"'';
|
||||||
};
|
description = ''
|
||||||
|
Path to the brand kit directory consumed by the build. Defaults
|
||||||
gitRef = lib.mkOption {
|
to the webapp flake's committed aiolabs brand. Override to a
|
||||||
type = lib.types.str;
|
per-host directory in server-deploy (e.g.
|
||||||
default = "main";
|
`./../branding/cfaun`) for per-host logo + manifest name +
|
||||||
example = "demo";
|
theme colors. See `branding/README.md` in the webapp repo.
|
||||||
description = "Git branch or tag to build from";
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
domain = lib.mkOption {
|
domain = lib.mkOption {
|
||||||
|
|
@ -137,23 +84,6 @@ in {
|
||||||
description = "Domain name for the webapp";
|
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
|
# Nostr Configuration
|
||||||
nostrRelays = lib.mkOption {
|
nostrRelays = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.listOf lib.types.str;
|
||||||
|
|
@ -308,24 +238,21 @@ in {
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
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} = {
|
services.nginx.virtualHosts.${cfg.domain} = {
|
||||||
# SSL configuration
|
|
||||||
forceSSL = cfg.enableSSL;
|
forceSSL = cfg.enableSSL;
|
||||||
enableACME = cfg.enableSSL;
|
enableACME = cfg.enableSSL;
|
||||||
|
|
||||||
# Serve the built webapp
|
root = "${webapp}/dist";
|
||||||
root = "${webapp}/share/webapp";
|
|
||||||
|
|
||||||
locations = {
|
locations = {
|
||||||
"/" = {
|
"/" = {
|
||||||
# Try files, fallback to index.html for SPA routing.
|
# SPA shell must revalidate on every load so new deploys are
|
||||||
# Everything matching this prefix ultimately serves the SPA
|
# picked up without the browser holding a stale shell pointing
|
||||||
# shell (index.html), which must revalidate on every load so
|
# at deleted hashed assets. The more-specific regex location
|
||||||
# new deploys are picked up without the browser holding a
|
# below overrides this for hashed static assets, which stay
|
||||||
# stale shell pointing at deleted hashed assets. The more-
|
# immutable.
|
||||||
# specific regex location below overrides this for hashed
|
|
||||||
# static assets, which stay immutable.
|
|
||||||
tryFiles = "$uri $uri/ /index.html";
|
tryFiles = "$uri $uri/ /index.html";
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
add_header Cache-Control "no-cache";
|
add_header Cache-Control "no-cache";
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue