1{
2 lib,
3 nodejs_20,
4 buildNpmPackage,
5 fetchFromGitHub,
6 writeShellScriptBin,
7 nss,
8 cmake,
9 pkg-config,
10 openssl,
11 libdatachannel,
12 plog,
13}:
14
15let
16 nodejs = nodejs_20;
17 buildNpmPackage' = buildNpmPackage.override { inherit nodejs; };
18
19 version = "1.20.1";
20
21 src = fetchFromGitHub {
22 owner = "httptoolkit";
23 repo = "httptoolkit-server";
24 rev = "refs/tags/v${version}";
25 hash = "sha256-iEAYZX7WNk6TvZ44GAOgTqXOcW5oFn4gX+kzixZZbWA=";
26 };
27
28 overridesNodeModules = buildNpmPackage' {
29 pname = "httptoolkit-server-overrides-node-modules";
30 inherit version src;
31 sourceRoot = "${src.name}/overrides/js";
32
33 npmDepsHash = "sha256-Uw7XbfwLMX+zbSrzFgvB8lw3hxUyw1eRKazCITrT/28=";
34
35 dontBuild = true;
36
37 installPhase = ''
38 mkdir -p $out
39 cp -r node_modules $out/node_modules
40 '';
41 };
42
43 nodeDatachannel = buildNpmPackage' {
44 pname = "node-datachannel";
45 version = "0.12.0";
46
47 src = fetchFromGitHub {
48 owner = "murat-dogan";
49 repo = "node-datachannel";
50 rev = "refs/tags/v${nodeDatachannel.version}";
51 hash = "sha256-xjYja+e2Z7X5cU4sEuSsJzG0gtmTPl3VrUf+ypd3zdw=";
52 };
53
54 npmFlags = [ "--ignore-scripts" ];
55
56 makeCacheWritable = true;
57
58 npmDepsHash = "sha256-Qhib9ZGulTXjoYcZIWunf3/BSd2SLXZuWEmMcstaphs=";
59
60 nativeBuildInputs = [
61 cmake
62 pkg-config
63 ];
64
65 buildInputs = [
66 openssl
67 libdatachannel
68 plog
69 ];
70
71 dontUseCmakeConfigure = true;
72
73 env.NIX_CFLAGS_COMPILE = "-I${nodejs}/include/node";
74 env.CXXFLAGS = "-include stdexcept"; # for GCC13
75
76 preBuild = ''
77 # don't use static libs and don't use FetchContent
78 # don't try to link plog (it's headers-only)
79 substituteInPlace CMakeLists.txt \
80 --replace-fail 'OPENSSL_USE_STATIC_LIBS TRUE' 'OPENSSL_USE_STATIC_LIBS FALSE' \
81 --replace-fail 'if(NOT libdatachannel)' 'if(false)' \
82 --replace-fail 'datachannel-static' 'datachannel' \
83 --replace-fail 'plog::plog' ""
84
85 # don't fetch node headers
86 substituteInPlace node_modules/cmake-js/lib/dist.js \
87 --replace-fail '!this.downloaded' 'false'
88
89 npm rebuild --verbose
90 '';
91
92 installPhase = ''
93 runHook preInstall
94 install -Dm755 build/Release/*.node -t $out/build/Release
95 runHook postInstall
96 '';
97 };
98in
99buildNpmPackage' {
100 pname = "httptoolkit-server";
101 inherit version src;
102
103 patches = [ ./only-build-for-one-platform.patch ];
104
105 npmDepsHash = "sha256-gHXop4CTsQTSMrZ5mBHkMcmpOr2MIjVLrzjLLCfZ3As=";
106
107 npmFlags = [ "--ignore-scripts" ];
108
109 makeCacheWritable = true;
110
111 nativeBuildInputs = [
112 # the build system uses the `git` executable to get the current revision
113 # we use a fake git to provide it with a fake revision
114 (writeShellScriptBin "git" "echo '???'")
115 ];
116
117 postConfigure = ''
118 # make sure `oclif-dev' doesn't fetch `node` binary to bundle with the app
119 substituteInPlace node_modules/@oclif/dev-cli/lib/tarballs/node.js --replace-fail \
120 'async function fetchNodeBinary({ nodeVersion, output, platform, arch, tmp }) {' \
121 'async function fetchNodeBinary({ nodeVersion, output, platform, arch, tmp }) { return;'
122
123 # manually place our prebuilt `node-datachannel` binary into its place, since we used '--ignore-scripts'
124 ln -s ${nodeDatachannel}/build node_modules/node-datachannel/build
125
126 cp -r ${overridesNodeModules}/node_modules overrides/js/node_modules
127
128 # don't run `npm ci` in `overrides/js` since we already copied node_modules into the directory
129 substituteInPlace prepare.ts --replace-fail "'ci', '--production'" "'--version'"
130
131 patchShebangs *.sh
132 '';
133
134 preBuild = ''
135 npm run build:src
136 '';
137
138 npmBuildScript = "build:release";
139
140 installPhase = ''
141 runHook preInstall
142
143 # we don't actually use any of the generated tarballs, we just copy from the tmp directory, since that's easier
144 mkdir -p $out/share/httptoolkit-server
145 cp -r build/tmp/httptoolkit-server/* -r $out/share/httptoolkit-server
146
147 # remove unneeded executables
148 rm -r $out/share/httptoolkit-server/bin/httptoolkit-server*
149
150 # since `oclif-dev pack` ran `npm install` again, we need to place the prebuilt binary here again
151 ln -s ${nodeDatachannel}/build $out/share/httptoolkit-server/node_modules/node-datachannel/build
152
153 # disable updating functionality
154 substituteInPlace $out/share/httptoolkit-server/node_modules/@oclif/plugin-update/lib/commands/update.js \
155 --replace-fail "await this.skipUpdate()" "'cannot update nix based package'"
156
157 # the app determines if it's in production by checking if HTTPTOOLKIT_SERVER_BINPATH is set to anything
158 makeWrapper $out/share/httptoolkit-server/bin/run $out/bin/httptoolkit-server \
159 --set HTTPTOOLKIT_SERVER_BINPATH dummy \
160 --prefix PATH : ${lib.makeBinPath [ nss.tools ]}
161
162 runHook postInstall
163 '';
164
165 passthru = {
166 inherit nodeDatachannel;
167 };
168
169 meta = {
170 description = "Backend for HTTP Toolkit";
171 homepage = "https://httptoolkit.com/";
172 license = lib.licenses.agpl3Plus;
173 mainProgram = "httptoolkit-server";
174 maintainers = with lib.maintainers; [ tomasajt ];
175 platforms = lib.platforms.unix;
176 };
177}