diff --git a/overlay/default.nix b/overlay/default.nix
index 9e0b45c..8bd0b1d 100644
--- a/overlay/default.nix
+++ b/overlay/default.nix
@@ -264,6 +264,8 @@ in nullPkgs // {
popt = null;
} (overrideAttrsHost (_: { outputs = [ "out" "man" ]; }) rsync);
+ sculptUtils = callPackage ./sculpt-utils { };
+
solo5-tools = callPackage ./solo5-tools { };
stdenv = if prev.stdenv.hostPlatform.isGenode then
diff --git a/overlay/sculpt-utils/default.nix b/overlay/sculpt-utils/default.nix
new file mode 100644
index 0000000..d8b53e2
--- /dev/null
+++ b/overlay/sculpt-utils/default.nix
@@ -0,0 +1,110 @@
+{ lib, hostPlatform, dhall, libxml2, runCommand, xz }:
+
+let arch = hostPlatform.uname.processor;
+in rec {
+
+ storeVersion = builtins.substring 11 7;
+
+ buildBinArchive = inputPkg:
+ runCommand "bin-${arch}-${inputPkg.pname}-${inputPkg.version}" (rec {
+ inherit arch inputPkg;
+ inherit (inputPkg) pname;
+ version = builtins.substring 11 7 inputPkg;
+ nativeBuildInputs = [ dhall xz ];
+ depotPath = "bin/${arch}/${pname}/${version}";
+ srcPath = "src/${pname}/${version}";
+ }) ''
+ mkdir -p archive/$version $out/bin/$arch/$pname
+ pushd archive/$version
+ export XDG_CACHE_HOME=$NIX_BUILD_TOP
+ eval $(echo "${
+ ./generate-manifest-link-script.dhall
+ } (toMap $inputPkg/nix-support/eris-manifest.dhall)" | dhall text)
+ # Need copies of truncated URNs because various Genode subsystems do this
+ for urn in urn:eris:*
+ do
+ cp ''${urn} ''${urn:0:63}
+ cp ''${urn} ''${urn:0:59}
+ done
+ popd
+ pushd archive
+ tar -c --xz -f $out/$depotPath.tar.xz --dereference .
+ popd
+ '';
+
+ buildIndex = { release, supportsArch, indexes }:
+ let
+ supportsArch' =
+ lib.strings.concatMapStrings (arch: '''')
+ supportsArch;
+
+ pkgToString = { info, path }: '''';
+ indexToString = name:
+ { pkgs }:
+ ''${toString (map pkgToString pkgs)}'';
+
+ indexes' = toString (lib.attrsets.mapAttrsToList indexToString indexes);
+
+ in runCommand "index-${release}" { nativeBuildInputs = [ libxml2 ]; } ''
+ mkdir -p $out/index
+ xmllint -format - << EOF | xz > $out/index/${release}.xz
+
+ ${supportsArch'}
+ ${indexes'}
+
+
+ EOF
+ '';
+
+ tagLabelToString = { tag, label ? null }:
+ "<${tag} ${lib.optionalString (label != null) ''label="${label}"''}/>";
+
+ runtimeToString = { binary, caps, ram, requires ? [ ], content ? [ ]
+ , config ? "", provides ? [ ] }:
+ ''
+
+ ''
+
+ + (lib.optionalString (requires != [ ]) ''
+ ${toString (map tagLabelToString requires)}
+ '')
+
+ + (lib.optionalString (provides != [ ]) ''
+ ${toString (map (p: "<${p}/>") provides)}
+ '')
+
+ + (lib.optionalString (content != [ ]) ''
+ ${toString (map tagLabelToString content)}
+ '')
+
+ + ''
+ ${config}
+
+ '';
+
+ buildPkg = { pname, archives, runtime }:
+ let
+ drv = runCommand "pkg-${pname}" {
+ inherit pname;
+ nativeBuildInputs = [ libxml2 xz ];
+ } ''
+ export version=''${out:11:7}
+ mkdir -p $version
+ awk 'NF' << EOF > $version/archives
+ ${lib.strings.concatMapStrings (s: s + "\n") archives}
+ EOF
+
+ xmllint -format - << EOF > $version/runtime
+ ${runtimeToString runtime}
+
+ EOF
+
+ mkdir -p $out/pkg/$pname
+ tar -c --xz -f $out/pkg/$pname/$version.tar.xz $version
+ '';
+ in drv // (rec {
+ version = storeVersion drv;
+ depotPath = "pkg/${drv.pname}/${version}";
+ });
+
+}
diff --git a/overlay/sculpt-utils/generate-manifest-link-script.dhall b/overlay/sculpt-utils/generate-manifest-link-script.dhall
new file mode 100644
index 0000000..c7e06f4
--- /dev/null
+++ b/overlay/sculpt-utils/generate-manifest-link-script.dhall
@@ -0,0 +1,25 @@
+let ClosureEntry = { cap : Text, path : Text }
+
+let Entry =
+ { mapKey : Text, mapValue : { cap : Text, closure : List ClosureEntry } }
+
+in λ(manifest : List Entry) →
+ List/fold
+ Entry
+ manifest
+ Text
+ ( λ(entry : Entry) →
+ λ(script : Text) →
+ ''
+ ${List/fold
+ ClosureEntry
+ entry.mapValue.closure
+ Text
+ ( λ(entry : ClosureEntry) →
+ λ(script : Text) →
+ "cp -u ${entry.path} ${entry.cap}; ${script}"
+ )
+ "true;"} cp ${entry.mapValue.cap} ${entry.mapKey}; ${script}
+ ''
+ )
+ "true"