diff --git a/build-rootfs.sh b/build-rootfs.sh new file mode 100755 index 0000000..0c5c8f2 --- /dev/null +++ b/build-rootfs.sh @@ -0,0 +1,35 @@ +source $stdenv/setup + +set -o pipefail + +# basic file system layout +mkdir -p $out/etc $out/proc $out/sys $out/dev $out/run $out/tmp $out/var/tmp $out/var/lib $out/var/log + +# empty files to mount over with host's version +touch $out/etc/resolv.conf $out/etc/machine-id + +# required for portable services +cp ${osRelease} $out/etc/os-release + + +# units must be copied to /etc/… +mkdir -p $out/etc/systemd/system +units=($units) +unitNames=($unitNames) +for ((n = 0; n < ${#units[*]}; n++)); do + unit=${units[$n]} + unitName=${unitNames[$n]} + cp $unit $out/etc/systemd/system/$unitName +done + + +# symlinks +objects=($objects) +targets=($targets) +for ((n = 0; n < ${#objects[*]}; n++)); do + object=${objects[$n]} + target=${targets[$n]} + + mkdir -p $(dirname $out/$target) + ln -s $object $out/$target +done diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..2d1aa6c --- /dev/null +++ b/default.nix @@ -0,0 +1,82 @@ +{ pkgs, lib, stdenv }: +/* + Create a systemd portable service image + https://systemd.io/PORTABLE_SERVICES/ + + Example: + … + pkgs.portableService { + name = "hello"; + version = "2.4.3"; + description = "hello portable"; + + units = [ hello-service ./files/hello.socket ]; + + symlinks = [ + { object = "${pkgs.cacert}/etc/ssl"; symlink = "/etc/ssl"; } + { object = "${pkgs.bash}/bin/bash"; symlink = "/bin/sh"; } + ]; + } + … +*/ +{ name +, version ? "dev" +, description ? null +, homepage ? null +, units ? [ ] +, symlinks ? [ ] +, contents ? [ ] +, squashfsTools ? pkgs.squashfsTools +, squash-compression ? "xz -Xdict-size 100%" +, squash-block-size ? "1M" +}: + +let + image-name = "${name}_${version}"; + + os-release-params = lib.filterAttrs (n: v: v != null) { + PORTABLE_ID = name; + PORTABLE_PRETTY_NAME = description; + HOME_URL = homepage; + ID = "nixos"; + PRETTY_NAME = "NixOS"; + BUILD_ID = "rolling"; + }; + + os-release = pkgs.writeText "os-release" (lib.generators.toKeyValue {} os-release-params); + + getUnitName = u: + if lib.isDerivation u then u.name + else if builtins.isPath u then baseNameOf u + else throw "unit must be either derivation or path"; + + rootfs = stdenv.mkDerivation { + name = "rootfs"; + builder = ./build-rootfs.sh; + inherit units; + unitNames = map getUnitName units; + osRelease = os-release; + objects = map (x: x.object) symlinks; + targets = map (x: x.symlink) symlinks; + }; +in + +stdenv.mkDerivation { + name = "${image-name}.raw"; + nativeBuildInputs = [ squashfsTools ]; + + buildCommand = '' + closureInfo=${pkgs.closureInfo { rootPaths = contents ++ [rootfs]; }} + + mkdir -p nix/store + for i in $(< $closureInfo/store-paths); do + cp -a "$i" "''${i:1}" + done + + mksquashfs nix ${rootfs}/* $out \ + -quiet -noappend \ + -keep-as-directory \ + -all-root -root-mode 755 \ + -b ${squash-block-size} -comp ${squash-compression} + ''; +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..b83ecea --- /dev/null +++ b/flake.nix @@ -0,0 +1,7 @@ +{ + description = "Nix tools to help you build portable services"; + + outputs = { self, nixpkgs, utils }: { + overlay = final: prev: prev.callPackage ./. { }; + }; +}