webapp-module/webapp.nix

242 lines
6.6 KiB
Nix

# NixOS module for building and serving the Vue 3 webapp
{ config, lib, pkgs, ... }:
let
cfg = config.services.webapp;
# Build the Vue 3 webapp using buildNpmPackage
webapp = pkgs.buildNpmPackage {
pname = "aio-webapp";
version = "1.0.0";
# Fetch source from git repository
src = builtins.fetchGit {
url = cfg.gitUrl;
ref = cfg.gitRef;
};
# SHA256 hash of npm dependencies
# Run `nix-prefetch-npm-deps package-lock.json` to get this hash
# Or use lib.fakeHash initially and let the build tell you the correct hash
npmDepsHash = cfg.npmDepsHash;
# Node.js version (use LTS)
nodejs = pkgs.nodejs_20;
# Include devDependencies (vue-tsc, vite, etc. are needed for build)
npmFlags = [ "--include=dev" ];
makeCacheWritable = true;
# 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_API_KEY = cfg.apiKey;
VITE_LNBITS_DEBUG = if cfg.lnbitsDebug then "true" else "false";
VITE_WEBSOCKET_ENABLED = if cfg.websocketEnabled then "true" else "false";
# 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;
# Additional env vars
NODE_ENV = "production";
# Explicitly add node_modules/.bin to PATH for vue-tsc, vite, etc.
buildPhase = ''
export PATH="$PWD/node_modules/.bin:$PATH"
npm run build
'';
# 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 npm 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";
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";
};
domain = lib.mkOption {
type = lib.types.str;
example = "app.example.com";
description = "Domain name for the webapp";
};
npmDepsHash = lib.mkOption {
type = lib.types.str;
default = lib.fakeHash;
description = ''
SHA256 hash of npm dependencies.
Run `nix-prefetch-npm-deps package-lock.json` on the webapp source
to get the correct hash.
'';
};
# 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;
default = [ "wss://relay.damus.io" "wss://relay.snort.social" ];
description = "List of Nostr relay URLs";
};
adminPubkeys = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
description = "List of admin Nostr public keys (hex format)";
};
# API Configuration
lnbitsBaseUrl = lib.mkOption {
type = lib.types.str;
example = "https://lnbits.example.com";
description = "LNBits API base URL";
};
apiKey = lib.mkOption {
type = lib.types.str;
default = "";
description = "LNBits invoice/read API key";
};
lnbitsDebug = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enable LNBits debug mode";
};
websocketEnabled = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Enable WebSocket for real-time updates";
};
# Lightning Address Domain
lightningDomain = lib.mkOption {
type = lib.types.str;
default = "";
description = ''
Domain used for Lightning Addresses.
Example: mydomain.com will show addresses as username@mydomain.com
'';
};
# Push Notifications
vapidPublicKey = lib.mkOption {
type = lib.types.str;
default = "";
description = "VAPID public key for push notifications";
};
pushNotificationsEnabled = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Enable push notifications";
};
# Image Upload Configuration (pict-rs)
pictrsBaseUrl = lib.mkOption {
type = lib.types.str;
default = "";
description = "Pict-rs image service base URL";
};
# Market Configuration
marketNaddr = lib.mkOption {
type = lib.types.str;
default = "";
description = "Nostr address (naddr) for the market configuration";
};
enableSSL = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Enable SSL/TLS with Let's Encrypt";
};
acmeEmail = lib.mkOption {
type = lib.types.str;
default = "";
description = "Email for Let's Encrypt certificate notifications";
};
};
config = lib.mkIf cfg.enable {
# Nginx virtualHost for webapp (global nginx settings are in config/nginx.nix)
services.nginx.virtualHosts.${cfg.domain} = {
# SSL configuration
forceSSL = cfg.enableSSL;
enableACME = cfg.enableSSL;
# Serve the built webapp
root = "${webapp}/share/webapp";
locations = {
"/" = {
# Try files, fallback to index.html for SPA routing
tryFiles = "$uri $uri/ /index.html";
};
# Cache static assets aggressively
"~* \\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$" = {
extraConfig = ''
expires 1y;
add_header Cache-Control "public, immutable";
'';
};
};
};
# ACME and firewall are configured globally in config/nginx.nix
};
}