diff --git a/README.md b/README.md index f604002..67d9911 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,10 @@ It now also carries the first Tribes deployment substrate: - `tribes/packages/release.scm` A deployment-bridge package wrapper for a prebuilt Tribes release tree. - `tribes/packages/source.scm` - A real source-built Tribes package that produces a production release from a - vendored Mix dependency tree plus local Parrhesia source. + A real source-built Tribes package that produces a production release from + vendored Mix and npm dependency trees plus local Parrhesia source. + Local-source builds accept hash overrides via `TRIBES_MIX_DEPS_SHA256`, + `TRIBES_RAW_MIX_DEPS_SHA256`, and `TRIBES_NPM_DEPS_SHA256`. - `tribes/services/tribes.scm` Shepherd service, runtime environment wiring, and account/activation setup for a Tribes node. diff --git a/manifests/substitutes/tribes-node.scm b/manifests/substitutes/tribes-node.scm index 26084f8..58e8c4e 100644 --- a/manifests/substitutes/tribes-node.scm +++ b/manifests/substitutes/tribes-node.scm @@ -27,7 +27,8 @@ source-directory #:version (getenv/default "TRIBES_RELEASE_VERSION" "dev") #:mix-deps-sha256 (getenv "TRIBES_MIX_DEPS_SHA256") - #:raw-mix-deps-sha256 (getenv "TRIBES_RAW_MIX_DEPS_SHA256")) + #:raw-mix-deps-sha256 (getenv "TRIBES_RAW_MIX_DEPS_SHA256") + #:npm-deps-sha256 (getenv "TRIBES_NPM_DEPS_SHA256")) tribes-package))) (define (make-tribes-node-manifest) diff --git a/tribes/packages/source.scm b/tribes/packages/source.scm index 7d6bec0..35d078a 100644 --- a/tribes/packages/source.scm +++ b/tribes/packages/source.scm @@ -11,12 +11,14 @@ #:use-module (gnu packages gawk) #:use-module (gnu packages linux) #:use-module (gnu packages m4) + #:use-module (gnu packages node) #:use-module (gnu packages perl) #:use-module (gnu packages pkg-config) #:use-module ((tribes packages mix) #:prefix mix:) #:use-module (srfi srfi-1) #:use-module (srfi srfi-13) #:export (fetch-mix-deps + fetch-npm-deps local-tribes-package tribes-package tribes-source-package @@ -34,6 +36,12 @@ (define %tribes-mix-deps-sha256 "0ksjnc9gnjijp1nbz3jlvl9kz8w7hx1a0ssms1dvd15rr25gn0d4") +;; Recursive sha256 of assets/node_modules generated from assets/package-lock.json +;; in an isolated build environment, with local file dependencies resolved from +;; the vendored Mix dependency tree. +(define %tribes-npm-deps-sha256 + "1bfzs67ffhwcm0dwdkb1jqnbn3fpgj22zfhd2y907w8daj62gahv") + (define %tribes-home-page "https://git.teralink.net/tribes/tribes.git") @@ -68,6 +76,16 @@ (sha256 (base32 "10cvh8jks3rjg6p7y0vm1v4kw9y7vljbfijj0zxwkxzysxx60w0f")))) +(define %heroicons-upstream-source + (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/tailwindlabs/heroicons") + (commit "0435d4ca364a608cc75e2f8683d374e55abbae26"))) + (file-name (git-file-name "heroicons" "2.2.0")) + (sha256 + (base32 "15di4p755ydivkbv9mv9hb8lsdgzb5zq77ljdnyp76cvykanpk15")))) + (define %excluded-root-basenames '(".cache" ".claude" @@ -125,6 +143,86 @@ checkout." (define fetch-mix-deps mix:fetch-mix-deps) +(define* (fetch-npm-deps source + #:key + mix-fod-deps + (name "tribes-npm-deps") + (version "0.2.0") + (sha256 %tribes-npm-deps-sha256)) + "Return a fixed-output node_modules tree for SOURCE/assets/package-lock.json, +with local file dependencies resolved from MIX-FOD-DEPS." + (computed-file + (string-append name "-" version) + (with-imported-modules '((guix build utils)) + #~(begin + (use-modules (guix build utils)) + + (define out #$output) + (define work (string-append (getcwd) "/build")) + (define app-dir (string-append work "/app")) + (define deps-dir (string-append app-dir "/deps")) + (define assets-dir (string-append app-dir "/assets")) + (define certs-dir + #$(file-append nss-certs "/etc/ssl/certs")) + (define cert-file + (string-append work "/ca-certificates.crt")) + (define path + (string-join + (list #$(file-append node "/bin") + #$(file-append bash-minimal "/bin") + #$(file-append coreutils "/bin") + #$(file-append findutils "/bin") + #$(file-append git-minimal "/bin") + #$(file-append gzip "/bin") + #$(file-append tar "/bin") + (or (getenv "PATH") "")) + ":")) + + (mkdir-p work) + (copy-recursively #+source app-dir #:follow-symlinks? #t) + (invoke #$(file-append coreutils "/bin/chmod") "-R" "u+w" app-dir) + + (when (file-exists? deps-dir) + (delete-file-recursively deps-dir)) + (copy-recursively #+mix-fod-deps deps-dir #:follow-symlinks? #t) + (invoke #$(file-append coreutils "/bin/chmod") "-R" "u+w" deps-dir) + + (invoke #$(file-append bash-minimal "/bin/sh") + "-c" + (string-append + #$(file-append coreutils "/bin/cat") + " " + certs-dir + "/*.pem > " + cert-file)) + + (setenv "PATH" path) + (setenv "HOME" (string-append work "/home")) + (setenv "XDG_CACHE_HOME" (string-append work "/cache")) + (setenv "npm_config_cache" (string-append work "/npm-cache")) + (setenv "npm_config_userconfig" (string-append work "/npmrc")) + (setenv "SSL_CERT_DIR" certs-dir) + (setenv "SSL_CERT_FILE" cert-file) + (setenv "NODE_ENV" "production") + + (mkdir-p (getenv "HOME")) + (mkdir-p (getenv "XDG_CACHE_HOME")) + (mkdir-p (getenv "npm_config_cache")) + + (with-directory-excursion assets-dir + (invoke "npm" "ci" "--ignore-scripts" "--no-audit" "--no-fund")) + + (mkdir-p out) + (copy-recursively (string-append assets-dir "/node_modules") + out + #:follow-symlinks? #t))) + #:options + `(#:hash ,(base32 sha256) + #:hash-algo sha256 + #:recursive? #t + #:leaked-env-vars ("http_proxy" "https_proxy" + "LC_ALL" "LC_MESSAGES" "LANG" "COLUMNS")))) + (define* (tribes-mix-deps source #:key (name "tribes-mix-deps") @@ -192,22 +290,32 @@ resolution by injecting extra pre-fetched sources needed for offline builds." (mix-deps-sha256 %tribes-mix-deps-sha256) (raw-mix-deps-sha256 %tribes-raw-mix-deps-sha256) + (npm-deps #f) + (npm-deps-sha256 %tribes-npm-deps-sha256) (name "tribes") (version %tribes-version) (home-page %tribes-home-page) (synopsis "Tribes social app") (description "Tribes social application built from source as a -production Elixir release using a vendored Mix dependency tree.")) +production Elixir release using vendored Mix and npm dependency trees.")) "Return a Guix package that builds a production Tribes release from SOURCE, -using MIX-DEPS as the pre-fetched Mix dependency tree resolved from mix.lock." - (let ((mix-deps-source - (or mix-deps - (tribes-mix-deps source - #:name (string-append name "-mix-deps") +using MIX-DEPS and NPM-DEPS as pre-fetched dependency trees resolved from +mix.lock and assets/package-lock.json." + (let* ((mix-deps-source + (or mix-deps + (tribes-mix-deps source + #:name (string-append name "-mix-deps") + #:version version + #:sha256 mix-deps-sha256 + #:raw-sha256 raw-mix-deps-sha256))) + (npm-deps-source + (or npm-deps + (fetch-npm-deps source + #:mix-fod-deps mix-deps-source + #:name (string-append name "-npm-deps") #:version version - #:sha256 mix-deps-sha256 - #:raw-sha256 raw-mix-deps-sha256)))) + #:sha256 npm-deps-sha256)))) (mix:mix-release-package source #:mix-fod-deps mix-deps-source @@ -228,6 +336,7 @@ using MIX-DEPS as the pre-fetched Mix dependency tree resolved from mix.lock." libtool linux-libre-headers m4 + node perl pkg-config sed) @@ -241,6 +350,7 @@ using MIX-DEPS as the pre-fetched Mix dependency tree resolved from mix.lock." gnu-make libtool m4 + node perl pkg-config sed) @@ -284,6 +394,51 @@ using MIX-DEPS as the pre-fetched Mix dependency tree resolved from mix.lock." (setenv "ERL_FLAGS" existing-erl-flags) (unsetenv "ERL_FLAGS"))) (invoke "mix" "deps.compile")) + #:build-gexp + #~(begin + (invoke "mix" "compile" "--no-deps-check") + + (let ((assets-node-modules "assets/node_modules")) + (when (file-exists? assets-node-modules) + (delete-file-recursively assets-node-modules)) + (copy-recursively #+npm-deps-source + assets-node-modules + #:follow-symlinks? #t) + (invoke #$(file-append coreutils "/bin/chmod") + "-R" + "u+w" + assets-node-modules) + (invoke "find" + assets-node-modules + "-type" "f" + "-path" "*/@esbuild/*/bin/esbuild" + "-exec" "chmod" "+x" "{}" "+") + (invoke "find" + assets-node-modules + "-type" "f" + "-path" "*/.bin/*" + "-exec" "chmod" "+x" "{}" "+")) + + (let ((heroicons-dir "deps/heroicons")) + (when (file-exists? heroicons-dir) + (delete-file-recursively heroicons-dir)) + (copy-recursively #+%heroicons-upstream-source + heroicons-dir + #:follow-symlinks? #t) + (invoke #$(file-append coreutils "/bin/chmod") + "-R" + "u+w" + heroicons-dir)) + + (setenv "NODE_PATH" + (string-append (getcwd) "/deps:" + (getcwd) "/_build/prod")) + + (with-directory-excursion "assets" + (invoke "npm" "run" "build.css" "--" "--minify") + (invoke "npm" "run" "build.js" "--" "--minify")) + + (invoke "mix" "phx.digest")) #:install-gexp #~(begin (invoke "mix" "release" "--no-deps-check" "--path" out) @@ -296,16 +451,19 @@ using MIX-DEPS as the pre-fetched Mix dependency tree resolved from mix.lock." #:key (version "dev") (mix-deps-sha256 #f) - (raw-mix-deps-sha256 #f)) + (raw-mix-deps-sha256 #f) + (npm-deps-sha256 #f)) "Return a Tribes package built from a local source checkout. Hash overrides -allow development against a changed mix.lock without changing the canonical +allow development against changed lockfiles without changing the canonical package pin." (tribes-source-package (tribes-source-directory->local-file directory) #:version version #:mix-deps-sha256 (or mix-deps-sha256 %tribes-mix-deps-sha256) #:raw-mix-deps-sha256 - (or raw-mix-deps-sha256 %tribes-raw-mix-deps-sha256))) + (or raw-mix-deps-sha256 %tribes-raw-mix-deps-sha256) + #:npm-deps-sha256 + (or npm-deps-sha256 %tribes-npm-deps-sha256))) (define tribes-package (tribes-source-package %tribes-upstream-source diff --git a/tribes/system/installer.scm b/tribes/system/installer.scm index 0d09670..98067fd 100644 --- a/tribes/system/installer.scm +++ b/tribes/system/installer.scm @@ -51,7 +51,8 @@ source-directory #:version (getenv/default "TRIBES_RELEASE_VERSION" "dev") #:mix-deps-sha256 (getenv "TRIBES_MIX_DEPS_SHA256") - #:raw-mix-deps-sha256 (getenv "TRIBES_RAW_MIX_DEPS_SHA256")) + #:raw-mix-deps-sha256 (getenv "TRIBES_RAW_MIX_DEPS_SHA256") + #:npm-deps-sha256 (getenv "TRIBES_NPM_DEPS_SHA256")) tribes-package))) (define* (tribes-installer-operating-system #:key