Files
inter-hub/flake.nix
tegwick 11ff61c1ba
Some checks failed
Build and Deploy / build-push-deploy (push) Has been cancelled
fix(build): route TH to ghc-iserv-dyn to bypass truncated libHSghc.a
Root cause: libHSghc-9.10.3-5702.a (287,768,576 bytes) has its last AR
entry (Expr.o) claiming 517,544 bytes but only 82,258 bytes remain —
the archive is truncated. GHC's internal readAr (Data.Binary.Get) panics
at position 287,686,318 when it tries to read the full claimed size.

The truncated .a is read lazily: IHP's TH splices queue a dependency on
the ghc package, which flushes to readAr after all 477 modules compile.
This explains the invariant crash at [477 of 477] WidgetVersionInclude.

ghc-iserv-dyn is not exposed in ghc-with-packages/bin/ (why
-fexternal-interpreter alone silently fell back to the internal linker).
Use -pgmi with the absolute path in the unwrapped GHC store to force
iserv-dyn, which uses dlopen on libHSghc.so (intact, 110 MB) instead
of readAr on the truncated .a. No crash.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 11:11:07 +02:00

245 lines
15 KiB
Nix

{
inputs = {
ihp.url = "github:digitallyinduced/ihp/v1.5";
nixpkgs.follows = "ihp/nixpkgs";
nixpkgs-nixos.follows = "ihp/nixpkgs-nixos";
flake-parts.follows = "ihp/flake-parts";
devenv.follows = "ihp/devenv";
systems.follows = "ihp/systems";
devenv-root = {
url = "file+file:///dev/null";
flake = false;
};
};
outputs = inputs@{ self, nixpkgs, nixpkgs-nixos, ihp, flake-parts, systems, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = import systems;
imports = [ ihp.flakeModules.default ];
perSystem = { pkgs, config, lib, ... }: {
ihp = {
appName = "inter-hub";
enable = true;
projectPath = ./.;
packages = with pkgs; [
tailwindcss
];
haskellPackages = p: with p; [
# Haskell dependencies go here
p.ihp
base
wai
text
# ihp-mail # Email support: https://ihp.digitallyinduced.com/Guide/mail.html
# ihp-datasync # Real-time DataSync
# ihp-job-dashboard # Job dashboard UI
# ihp-typed-sql # Type-safe SQL queries
# ihp-pglistener # PostgreSQL LISTEN/NOTIFY
# Phase 5: Anthropic API calls
http-conduit
aeson
string-conversions
# Phase 9: External API, crypto, SDK generation
cryptohash-sha256
base16-bytestring
random-bytestring
yaml
network-uri
];
devHaskellPackages = p: with p; [
cabal-install
hlint
hspec
ihp-hspec
];
# Hoogle documentation server — disabled to save ~400 MB on constrained host
withHoogle = false;
# Disable relation type machinery for faster compilation
# relationSupport = false;
# Skip tests/haddock for specific packages to speed up builds
# dontCheckPackages = [ "my-package" ];
# doJailbreakPackages = [ "my-package" ];
# dontHaddockPackages = [ "my-package" ];
# Production build tuning
# optimizationLevel = "2"; # Default: "1", use "2" for more optimized production binaries
# rtsFlags = "-A96m -N"; # GHC runtime flags for compiled binaries
# Mount additional directories under /static/ in production builds
# static.extraDirs = {
# # Frontend = self.packages.${system}.frontend;
# };
# static.makeBundling = true; # Set false if not using Makefile for CSS/JS bundling
};
# OCI container image for Kubernetes deployment (Railiance01).
# Build: nix build .#docker
# Push: skopeo copy docker-archive:result docker://92.205.130.254:32166/coulomb/inter-hub:SHA
# Uses IHP's built-in unoptimized image; binary is /bin/RunProdServer.
packages.docker = config.packages.unoptimized-docker-image;
# Custom configuration that will start with `devenv up`
devenv.shells.default = {
# Start Mailhog on local development to catch outgoing emails
# services.mailhog.enable = true;
# PostgreSQL extensions
# services.postgres.extensions = extensions: [ extensions.postgis ];
# GHC 9.10.3 crash fix: Generated.ActualTypes uses `module M` re-export
# syntax for 61 sub-modules; the resulting ActualTypes.hi exceeds GHC's
# ~274 MB binary-deserialization limit (crash at position 287686318).
#
# pkgs is built from `import nixpkgs { overlays = devenv.shells.default.overlays; }`.
# IHP adds ihp.overlays.default to this list, which sets
# pkgs.ghc = haskellPackages.override { overrides = ihpOverrides }.
# We extend pkgs.ghc with a mkDerivation override (lib.mkAfter ensures
# we run after IHP's overlay, so prev.ghc is already IHP's package set).
# For inter-hub-models: rewrite ActualTypes.hs export list from (module N)
# syntax to explicit T(..) re-exports — keeps hub functional for qualified
# references (Generated.ActualTypes.T) while producing compact .hi.
# Generated.Types stubbed; inter-hub-lib replaces its import with direct
# entity imports (sourceRoot is per-package, originals intact there).
overlays = lib.mkAfter [
(final: prev: {
ghc = prev.ghc.extend (hfinal: hprev: {
mkDerivation = args:
let drv = hprev.mkDerivation args;
in if (args.pname or "") == "inter-hub-models"
then drv.overrideAttrs (old: {
# GHC 9.10.3 crash: `module M` re-export syntax in Generated.ActualTypes
# embeds 61 full sub-interfaces into ActualTypes.hi (~287 MB), exceeding
# GHC's 274 MB binary read limit.
#
# Fix: rewrite the hub's export list from `module M` syntax to explicit
# T(..) re-exports. Explicit re-exports store only name references in
# the .hi file (compact); `module M` embeds the full sub-interface.
# Hub stays functional (consumers still qualify via Generated.ActualTypes),
# but .hi stays small.
configureFlags = (old.configureFlags or []) ++ [
"--ghc-option=-O0"
"--ghc-option=-fomit-interface-pragmas"
"--disable-split-sections"
"--ghc-option=-j1"
"--disable-shared"
# GHC 9.10.3 bug: libHSghc-9.10.3-5702.a is truncated (last AR
# entry Expr.o claims 517544 bytes but only 82258 remain).
# GHC's internal static linker (readAr via Data.Binary.Get) panics
# after all 477 modules compile when it flushes deferred symbol
# loads from IHP's TH splices that transitively need the ghc pkg.
# Fix: delegate TH evaluation to ghc-iserv-dyn, which uses dlopen
# on libHSghc.so (intact) instead of readAr on the truncated .a.
# ghc-iserv-dyn is not in ghc-with-packages/bin/, so use -pgmi
# with the absolute path in the unwrapped GHC store path.
"--ghc-option=-fexternal-interpreter"
"--ghc-option=-pgmi"
"--ghc-option=${hprev.ghc}/lib/ghc-9.10.3/bin/ghc-iserv-dyn"
];
postUnpack = (old.postUnpack or "") + ''
_actual="$sourceRoot/build/Generated/ActualTypes.hs"
# Rewrite hub export list: (module N, ...) explicit names.
# IHP pattern: data Foo' params = Foo {...} (primed type, unprimed ctor)
# type Foo = Foo' arg1 arg2 (concrete alias, kind *)
# ADTs: export T(..) to include type + ctor + fields.
# type aliases: export T (no (..) not an ADT).
# type instance lines start with lowercase 'i', so don't match [A-Z].
_types=$(
{
awk '/^data [A-Z]|^newtype [A-Z]/{print $2"(..)"}
/^type [A-Z]/{print $2}' \
"$sourceRoot/build/Generated/Enums.hs"
find "$sourceRoot/build/Generated/ActualTypes" -name "*.hs" | \
sort | while IFS= read -r _m; do
awk '/^data [A-Z]|^newtype [A-Z]/{print $2"(..)"}
/^type [A-Z]/{print $2}' "$_m"
done
} | sort -u
)
_exports=$(echo "$_types" | \
awk 'NR==1{printf " %s", $0; next} {printf "\n , %s", $0} END{printf "\n"}')
_imports=$(awk '/^import Generated\./{print}' "$_actual")
{
printf 'module Generated.ActualTypes\n ( %s ) where\n' "$_exports"
printf '%s\n' "$_imports"
} > "$_actual.new" && mv "$_actual.new" "$_actual"
# Stub Generated.Types (inter-hub-lib reads original from its sourceRoot)
printf '%s\n' 'module Generated.Types () where' \
> "$sourceRoot/build/Generated/Types.hs"
'';
})
else if (args.pname or "") == "inter-hub-lib"
then drv.overrideAttrs (old: {
# Generated.Types is an empty stub in models — hub removed to avoid
# module M re-export crash. Replace every bare `import Generated.Types`
# with direct imports of the 119 individual entity modules.
# Generated.ActualTypes hub is still functional (explicit T(..) exports),
# so no patching needed for ActualTypes imports in this package.
postUnpack = (old.postUnpack or "") + ''
_types="$sourceRoot/build/Generated/Types.hs"
_imp=$(mktemp)
awk '/^import Generated\./{print "import " $2}' "$_types" > "$_imp"
find "$sourceRoot" -name "*.hs" | while IFS= read -r _f; do
if grep -qE "^import Generated\.Types$" "$_f"; then
awk -v imp="$_imp" '
/^import Generated\.Types$/{
while ((getline ln < imp) > 0) print ln
close(imp)
next
}
{ print }' "$_f" > "$_f.new" && mv "$_f.new" "$_f"
fi
done
rm -f "$_imp"
'';
})
else drv;
});
})
];
# Resource limits for constrained host (2 CPU, ~3.8 GiB RAM).
# -A32m: smaller minor heap (reduces GC pressure).
# -M2g: hard heap ceiling (prevents OOM on large compiles).
# Note: -N1 is intentionally omitted — it requires -threaded and
# would break build-generated-code and similar tools.
# GHC parallel module compilation is capped via -j1 in .ghci.
env.GHCRTS = "-A32m -M2g";
# Custom processes that don't appear in https://devenv.sh/reference/options/
processes = {
tailwind.exec = "tailwindcss -c tailwind/tailwind.config.js -i ./tailwind/app.css -o static/app.css --watch=always";
};
};
};
# Adding the new NixOS configuration for "production"
# See https://ihp.digitallyinduced.com/Guide/deployment.html#deploying-with-deploytonixos for more info
# Used to deploy the IHP application
flake.nixosConfigurations."production" = import ./Config/nix/hosts/production/host.nix { inherit inputs; };
};
# The following configuration speeds up build times by using the devenv, cachix and digitallyinduced binary caches
# You can add your own cachix cache here to speed up builds. For that uncomment the following lines and replace `CHANGE-ME` with your cachix cache name
nixConfig = {
extra-substituters = [
"https://devenv.cachix.org"
"https://cachix.cachix.org"
"https://digitallyinduced.cachix.org"
# "https://CHANGE-ME.cachix.org"
];
extra-trusted-public-keys = [
"devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="
"cachix.cachix.org-1:eWNHQldwUO7G2VkjpnjDbWwy4KQ/HNxht7H4SSoMckM="
"digitallyinduced.cachix.org-1:y+wQvrnxQ+PdEsCt91rmvv39qRCYzEgGQaldK26hCKE="
# "CHANGE-ME.cachix.org-1:CHANGE-ME-PUBLIC-KEY"
];
};
}