{ description = "Sausage: a world of WASI programs and durable agents"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; flake-utils.url = "github:numtide/flake-utils"; rust-overlay.url = "github:oxalica/rust-overlay"; rust-overlay.inputs.nixpkgs.follows = "nixpkgs"; crane.url = "github:ipetkov/crane"; kache = { url = "github:kunobi-ninja/kache/stable"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = { nixpkgs, flake-utils, rust-overlay, crane, kache, ... }: flake-utils.lib.eachSystem flake-utils.lib.allSystems (system: let pkgs = import nixpkgs { inherit system; overlays = [ rust-overlay.overlays.default ]; }; inherit (pkgs) lib; rust = pkgs.rust-bin.stable.latest.default.override { targets = [ "wasm32-wasip2" ]; extensions = [ "rust-src" ]; }; craneLib = (crane.mkLib pkgs).overrideToolchain rust; src = ./. |> pkgs.nix-gitignore.gitignoreSource []; tools = ./userland/tools.json |> builtins.readFile |> builtins.fromJSON; commandArgs = tools |> builtins.filter (t: t.package != null) |> map (t: t.package) |> lib.unique |> map (p: "-p ${p}") |> lib.concatStringsSep " "; mold = lib.optionals pkgs.stdenv.hostPlatform.isLinux [ pkgs.mold ]; hostTarget = pkgs.stdenv.hostPlatform.rust.rustcTarget |> builtins.replaceStrings [ "-" ] [ "_" ] |> lib.toUpper; moldVar = "CARGO_TARGET_${hostTarget}_RUSTFLAGS"; moldEnv = lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux { ${moldVar} = "-C link-arg=-fuse-ld=mold"; }; builderTools = [ kache.packages.${system}.default pkgs.bubblewrap pkgs.nushell pkgs.wasm-tools pkgs.bash pkgs.coreutils ]; runtimePath = [ rust pkgs.stdenv.cc ] ++ builderTools ++ mold |> lib.makeBinPath; wasiCc = pkgs.pkgsCross.wasi32.stdenv.cc; wasiCcEnv = { CC_wasm32_wasip2 = "${wasiCc}/bin/${wasiCc.targetPrefix}cc"; AR_wasm32_wasip2 = "${wasiCc}/bin/${wasiCc.targetPrefix}ar"; }; v8Archive = pkgs.fetchurl { url = "https://github.com/denoland/rusty_v8/releases/download/v150.4.0/librusty_v8_simdutf_release_${pkgs.stdenv.hostPlatform.rust.rustcTarget}.a.gz"; hash = { x86_64-linux = "sha256-9IdiyhDR8fxgWkQcWuQw7Izh6egPFNePvELLh4wwtHY="; aarch64-linux = "sha256-U54oOBWjlqV5bzKFi0LlF7hY66rqqtBdAykO6MhkpSc="; x86_64-darwin = "sha256-p1AnH+xrIRRX7Qpc99LqsZJLJlYhqC2oarlZ1v8II+Q="; aarch64-darwin = "sha256-Wu/9jVoMG3msHXCvg9WxkJllX9nGRaeU3EPxAfd5g4w="; }.${system}; }; # rusty_v8's archive hook prevents downloads inside the Nix sandbox. v8Env = { RUSTY_V8_ARCHIVE = v8Archive; }; cwdInit = pkgs.runCommand "sausage-cwd-init" { nativeBuildInputs = [ rust pkgs.nushell pkgs.wasm-tools ]; } '' cp -r ${./userland/cwd-init} cwd-init chmod -R u+w cwd-init nu cwd-init/build.nu mkdir $out cp cwd-init/init.o $out/ ''; linker = pkgs.runCommand "sausage-wasi-link" { nativeBuildInputs = [ pkgs.nushell ]; } '' cp ${./userland/cwd-init/link.nu} $out chmod +x $out patchShebangs $out ''; common = { pname = "sausage"; version = "0.3.0"; inherit src; strictDeps = true; nativeBuildInputs = [ pkgs.pkg-config pkgs.cmake pkgs.python3 pkgs.perl pkgs.nushell pkgs.wasm-tools ]; # The WASI linker initializes each guest's cwd before main. SAUSAGE_CWD_INIT = "${cwdInit}/init.o"; CARGO_TARGET_WASM32_WASIP2_LINKER = linker; CARGO_TARGET_WASM32_WASIP2_RUSTFLAGS = "-C link-arg=sausage-cwd-init"; # Registry gix crates need the real APIs of the local WASI patches. extraDummyScript = '' chmod -R u+w $out/userland/git/vendor cp -r ${./userland/git/vendor}/. $out/userland/git/vendor/ ''; } // wasiCcEnv; hostArgs = common // v8Env // moldEnv // { cargoExtraArgs = "--locked -p sausage"; buildInputs = [ pkgs.openssl ]; nativeBuildInputs = common.nativeBuildInputs ++ mold; doCheck = false; }; hostArtifacts = craneLib.buildDepsOnly hostArgs; userlandArgs = common // { pname = "sausage-userland"; CARGO_BUILD_TARGET = "wasm32-wasip2"; cargoExtraArgs = "--locked ${commandArgs}"; doCheck = false; }; userlandArtifacts = craneLib.buildDepsOnly userlandArgs; userland = craneLib.buildPackage (userlandArgs // { cargoArtifacts = userlandArtifacts; }); goTool = name: source: subPackages: (pkgs.buildGoModule { pname = "sausage-${name}"; version = "vendored"; src = source; inherit subPackages; vendorHash = null; env.CGO_ENABLED = "0"; ldflags = [ "-s" "-w" ]; doCheck = false; dontStrip = true; postInstall = '' mv $out/bin/wasip1_wasm/* $out/bin/${name}.wasm rmdir $out/bin/wasip1_wasm ''; }).overrideAttrs (old: { env = old.env // { GOOS = "wasip1"; GOARCH = "wasm"; }; }); awk = goTool "awk" ./userland/goawk/source [ "." ]; esbuild = goTool "esbuild" ./userland/esbuild/source [ "cmd/esbuild" ]; qjs = pkgs.stdenv.mkDerivation { pname = "sausage-qjs"; version = "0.18.0"; src = ./userland/qjs/source; nativeBuildInputs = [ pkgs.cmake pkgs.llvmPackages_21.lld ]; cmakeFlags = [ "-DCMAKE_SYSTEM_NAME=WASI" "-DCMAKE_C_COMPILER=${wasiCcEnv.CC_wasm32_wasip2}" "-DCMAKE_AR=${wasiCcEnv.AR_wasm32_wasip2}" "-DCMAKE_RANLIB=${wasiCc}/bin/${wasiCc.targetPrefix}ranlib" ]; buildFlags = [ "qjs_exe" ]; installPhase = '' runHook preInstall mkdir -p $out/bin cp qjs $out/bin/qjs.wasm runHook postInstall ''; dontStrip = true; }; bundle = pkgs.runCommand "sausage-bundle" {} '' mkdir -p $out/bin $out/src/sausage $out/etc/sausage $out/etc/systemd/system/multi-user.target.wants $out/srv/www ${tools |> map (t: '' cp ${(if t.package != null then userland else { inherit awk esbuild qjs; }.${t.binary})}/bin/${t.binary}.wasm $out/bin/ ${t.names |> map (name: '' ${lib.optionalString (name != t.binary) "ln -s ${t.binary}.wasm $out/bin/${name}.wasm"} ln -s ${t.binary}.wasm $out/bin/${name} '') |> lib.concatStrings} '') |> lib.concatStrings} cp -R --no-preserve=mode ${src}/. $out/src/sausage/ cp ${./config/config.yaml} $out/etc/sausage/config.yaml cp -R ${./config/roster} $out/etc/sausage/roster cp ${./userland/web/web.service} $out/etc/systemd/system/web.service touch $out/etc/systemd/system/multi-user.target.wants/web.service cp ${./userland/web/index.html} ${./userland/web/credentials.js} ${./userland/web/credentials-view.js} $out/srv/www/ ''; sausageUnwrapped = craneLib.buildPackage (hostArgs // { cargoArtifacts = hostArtifacts; doNotRemoveReferencesToVendorDir = true; }); # Keep the wrapper separate from crane's removal of toolchain references. sausage = pkgs.symlinkJoin { name = "sausage-${sausageUnwrapped.version}"; paths = [ sausageUnwrapped ]; nativeBuildInputs = [ pkgs.makeWrapper ]; postBuild = '' wrapProgram $out/bin/sausage --set-default SAUSAGE_USERLAND ${bundle} \ --prefix PATH : ${runtimePath} \ --set-default CC_wasm32_wasip2 ${wasiCcEnv.CC_wasm32_wasip2} \ --set-default AR_wasm32_wasip2 ${wasiCcEnv.AR_wasm32_wasip2} \ ${lib.optionalString pkgs.stdenv.hostPlatform.isLinux "--set-default ${moldVar} '-C link-arg=-fuse-ld=mold'"} ''; meta.mainProgram = "sausage"; }; in { packages = { default = sausage; inherit bundle userland awk esbuild qjs; }; checks.package = sausage; checks.tests = craneLib.cargoTest (hostArgs // { cargoArtifacts = hostArtifacts; cargoExtraArgs = "--locked -p sausage -p sausage-vfs"; cargoTestExtraArgs = "-- --nocapture"; doCheck = true; nativeBuildInputs = hostArgs.nativeBuildInputs ++ builderTools ++ [ pkgs.git pkgs.nodejs ]; SAUSAGE_TEST_NO_BUILDER_SANDBOX = "1"; SAUSAGE_NIX_CHECK = "1"; SAUSAGE_USERLAND = bundle; preCheck = '' export SAUSAGE_CACHE="$TMPDIR/sausage-cache" export CARGO_TARGET_DIR="$PWD/target" export RUST_BACKTRACE=1 mkdir -p target/userland ln -s ${bundle} target/bundle ln -s ${bundle} target/userland/bundle ''; }); checks.shell = pkgs.runCommand "sausage-shell" {} '' export SAUSAGE_CACHE="$TMPDIR/sausage-cache" ${sausage}/bin/sausage sh "$TMPDIR/world" -c \ 'set -e; echo hi | tr a-z A-Z; git --version; rg --version; awk "BEGIN {print 42}"; echo payload > /payload; tar -cf /test.tar /payload; tar -tf /test.tar; qjs -e "console.log(42)"; cargo --help; undo list' > output cat output grep -qx HI output grep -q 'git version' output grep -q ripgrep output grep -qx 42 output grep -q payload output test -d "$TMPDIR/world/world/src/sausage/extensions" test -f "$TMPDIR/world/world/etc/sausage/roster/sausage.md" touch $out ''; apps.default = { type = "app"; program = "${sausage}/bin/sausage"; }; devShells.default = pkgs.mkShell (v8Env // wasiCcEnv // moldEnv // { packages = [ rust pkgs.wasmtime pkgs.openssl pkgs.pkg-config pkgs.go pkgs.cmake pkgs.llvmPackages_21.lld ] ++ builderTools ++ mold; }); }); }