From b30b37d5ce72f2aa0509e487d0a0a34e179f8f0e Mon Sep 17 00:00:00 2001 From: padreug Date: Thu, 15 Jan 2026 12:57:23 +0100 Subject: [PATCH] Initial commit: Vue 3 webapp NixOS module --- .gitignore | 1 + flake.nix | 10 +++ webapp.nix | 242 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 253 insertions(+) create mode 100644 .gitignore create mode 100644 flake.nix create mode 100644 webapp.nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b2be92b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +result diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..694a3ab --- /dev/null +++ b/flake.nix @@ -0,0 +1,10 @@ +{ + description = "Vue 3 webapp NixOS module"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + outputs = { self, nixpkgs }: { + nixosModules.webapp = import ./webapp.nix; + nixosModules.default = self.nixosModules.webapp; + }; +} diff --git a/webapp.nix b/webapp.nix new file mode 100644 index 0000000..bc5ac75 --- /dev/null +++ b/webapp.nix @@ -0,0 +1,242 @@ +# 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 + }; +}