singularity-forge/flake.nix
Mikael Hugo 0acb0f9be0
Some checks failed
sf self-deploy / deploy test and probe (push) Blocked by required conditions
sf self-deploy / promote prod (push) Blocked by required conditions
sf self-deploy / build, test, and publish server image (push) Has been cancelled
feat: harden sf server build and routing
2026-05-18 02:33:28 +02:00

319 lines
11 KiB
Nix

{
description = "Development and build environment for singularity-forge";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
flake-utils.url = "github:numtide/flake-utils";
};
nixConfig = {
extra-substituters = [
"https://cache.centralcloud.com/default"
];
extra-trusted-public-keys = [
"default:ywfU21WX06iOn2Ec2lae1jYh4w8LO4IQkmp06vJzsk8="
];
};
outputs = {
self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs {
inherit system;
};
lib = pkgs.lib;
nodejs26 = pkgs.stdenv.mkDerivation {
pname = "nodejs";
version = "26.1.0";
src = pkgs.fetchurl {
url = "https://nodejs.org/dist/v26.1.0/node-v26.1.0-linux-x64.tar.xz";
hash = "sha256-n8byG2xKYkOXJxI+UQ6cOf67L1Y3OPSSfNPgsojJs8k=";
};
nativeBuildInputs = [pkgs.autoPatchelfHook];
buildInputs = [pkgs.stdenv.cc.cc.lib];
installPhase = ''
runHook preInstall
mkdir -p "$out"
cp -R . "$out/"
ln -s "$out/bin/node" "$out/bin/nodejs"
substituteInPlace "$out/lib/node_modules/npm/bin/npm-cli.js" \
--replace-fail "#!/usr/bin/env node" "#!$out/bin/node"
substituteInPlace "$out/lib/node_modules/npm/bin/npx-cli.js" \
--replace-fail "#!/usr/bin/env node" "#!$out/bin/node"
runHook postInstall
'';
passthru.python = pkgs.python3;
};
source = lib.cleanSourceWith {
src = ./.;
filter = path: type: let
rel = lib.removePrefix "${toString ./.}/" (toString path);
base = baseNameOf path;
in
!(lib.hasPrefix ".git/" rel)
&& !(lib.hasPrefix "node_modules/" rel)
&& !(lib.hasPrefix "web/node_modules/" rel)
&& !(lib.hasPrefix "dist/" rel)
&& !(lib.hasPrefix ".next/" rel)
&& !(lib.hasPrefix "web/.next/" rel)
&& !(lib.hasPrefix "rust-engine/target/" rel)
&& base != ".direnv";
};
nativeNpmBuildInputs = with pkgs; [
git
libsecret
makeWrapper
pkg-config
python3
stdenv.cc
];
rootNodeModules = pkgs.importNpmLock.buildNodeModules {
npmRoot = ./.;
nodejs = nodejs26;
derivationArgs = {
nativeBuildInputs = nativeNpmBuildInputs;
buildInputs = [pkgs.libsecret pkgs.zlib pkgs.stdenv.cc.cc.lib];
npmInstallFlags = ["--ignore-scripts"];
npm_config_ignore_scripts = "true";
};
};
webNodeModules = pkgs.importNpmLock.buildNodeModules {
npmRoot = ./web;
nodejs = nodejs26;
derivationArgs = {
nativeBuildInputs = nativeNpmBuildInputs;
buildInputs = [pkgs.libsecret pkgs.zlib pkgs.stdenv.cc.cc.lib];
npmInstallFlags = ["--ignore-scripts"];
npm_config_ignore_scripts = "true";
};
};
sfServerRoot = pkgs.stdenv.mkDerivation {
pname = "sf-server-root";
version = "2.75.4";
src = source;
nativeBuildInputs = nativeNpmBuildInputs ++ [nodejs26];
buildInputs = [pkgs.libsecret pkgs.zlib pkgs.stdenv.cc.cc.lib];
SF_GIT_SHA = self.rev or self.dirtyRev or "dirty";
SF_GIT_REF = self.ref or "main";
SF_IMAGE_REPOSITORY = "registry.infra.centralcloud.com/singularity/sf-server";
buildPhase = ''
runHook preBuild
export CI=1
export HOME="$TMPDIR/home"
export npm_config_cache="$TMPDIR/npm-cache"
mkdir -p "$HOME" "$npm_config_cache"
mkdir -p node_modules
ln -s ${rootNodeModules}/node_modules/.bin node_modules/.bin
for entry in ${rootNodeModules}/node_modules/*; do
name="$(basename "$entry")"
if [ "$name" = "@singularity-forge" ]; then
mkdir -p node_modules/@singularity-forge
for scoped_entry in "$entry"/*; do
ln -s "$scoped_entry" "node_modules/@singularity-forge/$(basename "$scoped_entry")"
done
else
ln -s "$entry" "node_modules/$name"
fi
done
ln -s ${webNodeModules}/node_modules web/node_modules
node scripts/link-workspace-packages.cjs
npm run build:core
npm run build:web-host
npm run release:manifest -- --out dist/sf-release-manifest.json
runHook postBuild
'';
installPhase = ''
runHook preInstall
prune_runtime_node_modules() {
local modules="$1"
[ -d "$modules" ] || return 0
chmod -R u+w "$modules"
# Production server target: Linux x64 glibc on Vega. Keep runtime
# Linux x64 GNU native packages and remove optional packages for
# other OS/CPU/libc targets that npm lockfiles fetch for portability.
rm -rf \
"$modules/playwright" \
"$modules/playwright-core" \
"$modules/@playwright" \
"$modules/next/dist/experimental/testmode/playwright" \
"$modules/next/experimental/testmode/playwright" \
"$modules/node-pty/deps/winpty"
rm -f \
"$modules/.bin/playwright" \
"$modules/.bin/playwright-core" \
"$modules/next/experimental/testmode/playwright.js" \
"$modules/next/experimental/testmode/playwright.d.ts" \
"$modules/node-pty"/src/windows* \
"$modules/node-pty"/lib/windows* \
"$modules/@lydell/node-pty"/windows*
find "$modules" -mindepth 1 -maxdepth 8 -type d \( \
-name "*android*" -o \
-name "*darwin*" -o \
-name "*freebsd*" -o \
-name "*openbsd*" -o \
-name "*win32*" -o \
-name "*windows*" -o \
-name "*linux_ia32*" -o \
-name "*linux_arm*" -o \
-name "*linux-arm*" -o \
-name "*linux-arm64*" -o \
-name "*linux_loong*" -o \
-name "*linux-loong*" -o \
-name "*linux_riscv*" -o \
-name "*linux-riscv*" -o \
-name "*linuxmusl*" -o \
-name "*musl_x64*" -o \
-name "*arm64*" -o \
-name "*armhf*" -o \
-name "*armv7*" -o \
-name "*riscv*" -o \
-name "*loong*" -o \
-name "*ppc64*" -o \
-name "*s390x*" -o \
-name "*ia32*" -o \
-name "*musl*" -o \
-name "*wasm32*" \
\) ! \( \
-name "*linux-x64-gnu*" -o \
-name "*linux_x64*" -o \
-name "*linux-x64" -o \
-name "linux-x64" \
\) -prune -exec rm -rf {} +
find "$modules" -type d \( \
-name ".cache" -o \
-name "__tests__" -o \
-name "test" -o \
-name "tests" -o \
-name "docs" -o \
-name "examples" \
\) -prune -exec rm -rf {} +
}
mkdir -p "$out/opt/sf"
cp package.json package-lock.json README.md "$out/opt/sf/"
cp -R packages dist pkg src scripts rust-engine web "$out/opt/sf/"
cp -R ${rootNodeModules}/node_modules "$out/opt/sf/node_modules"
prune_runtime_node_modules "$out/opt/sf/node_modules"
rm -rf \
"$out/opt/sf/web/node_modules" \
"$out/opt/sf/web/.next/cache" \
"$out/opt/sf/web/.next/standalone"
cp -R ${webNodeModules}/node_modules "$out/opt/sf/web/node_modules"
prune_runtime_node_modules "$out/opt/sf/web/node_modules"
if [ -d "$out/opt/sf/dist/web/standalone/node_modules/@singularity-forge" ]; then
for pkg in "$out/opt/sf/dist/web/standalone/node_modules/@singularity-forge"/*; do
[ -e "$pkg" ] || continue
name="$(basename "$pkg")"
source="$out/opt/sf/packages/$name"
if [ -d "$source" ]; then
rm -rf "$pkg"
cp -R "$source" "$pkg"
fi
done
fi
prune_runtime_node_modules "$out/opt/sf/dist/web/standalone/node_modules"
find "$out/opt/sf" -name tsconfig.tsbuildinfo -delete
runHook postInstall
'';
};
node26SlimBase = pkgs.dockerTools.pullImage {
imageName = "node";
imageDigest = "sha256:424cafd2a035ed2b2d74acc3142b68b426fb62a47742c80a75e7117db02d6b30";
finalImageName = "node";
finalImageTag = "26.1-slim";
sha256 = lib.fakeSha256;
};
in {
packages = {
inherit nodejs26 rootNodeModules webNodeModules sfServerRoot;
sf-server-image = pkgs.dockerTools.streamLayeredImage {
name = "registry.infra.centralcloud.com/singularity/sf-server";
tag = self.rev or self.dirtyRev or "dirty";
fromImage = node26SlimBase;
contents = [
sfServerRoot
pkgs.ca-certificates
pkgs.git
pkgs.libsecret
pkgs.procps
pkgs.tini
];
config = {
WorkingDir = "/workspace";
ExposedPorts = {
"4000/tcp" = {};
};
Env = [
"NODE_ENV=production"
"SF_RELEASE_MANIFEST=/opt/sf/dist/sf-release-manifest.json"
"SF_WEB_PACKAGE_ROOT=/opt/sf"
"SF_WEB_PREFER_SOURCE=0"
"SF_WEB_HOST=0.0.0.0"
"SF_WEB_PORT=4000"
"HOSTNAME=0.0.0.0"
"PORT=4000"
];
Entrypoint = ["${pkgs.tini}/bin/tini" "--"];
Cmd = ["node" "/opt/sf/dist/web/standalone/server.js"];
};
};
default = sfServerRoot;
};
devShells.default = pkgs.mkShell {
packages = with pkgs; [
bash
cargo
clippy
git
just
libsecret
pkg-config
protobuf
rust-analyzer
rustc
rustfmt
uv
zlib
nodejs26
];
shellHook = ''
export SF_SOURCE_DIR="${toString ./.}"
if [ -x "$HOME/.local/bin/mise" ]; then
MISE_NODE_BIN="$(cd "$SF_SOURCE_DIR" && "$HOME/.local/bin/mise" which node 2>/dev/null || true)"
if [ -n "$MISE_NODE_BIN" ]; then
CLEAN_PATH="$(printf '%s' "$PATH" | tr ':' '\n' | grep -v '/mise/installs/node/.*/bin' | paste -sd: -)"
export PATH="$(dirname "$MISE_NODE_BIN"):$CLEAN_PATH"
fi
fi
export PATH="$SF_SOURCE_DIR/bin:$PATH"
export RUST_BACKTRACE=1
echo "singularity-forge development shell"
echo " cargo: $(command -v cargo)"
echo " node : repo-pinned by mise after direnv activation"
echo " protoc: $(command -v protoc)"
echo " rustc: $(command -v rustc)"
echo ""
echo "Build native addon:"
echo " node rust-engine/scripts/build.js"
'';
};
});
}