Compare commits

...

6 commits

Author SHA1 Message Date
19b60de3f2
escape json 2025-08-24 11:36:00 +02:00
3410c97729
use more common naming scheme 2025-08-24 11:36:00 +02:00
aa58480b33
add shorthands 2025-08-24 11:36:00 +02:00
959d3911a5
add json templating 2025-08-24 11:36:00 +02:00
lassulus
e46bc3ef80
Merge pull request #7 from KiaraGrouwstra/license-mit
add license: MIT
2025-08-24 10:41:07 +02:00
df2f3f7524
add license: MIT 2025-08-21 09:31:32 +02:00
6 changed files with 92 additions and 9 deletions

18
LICENSE.md Normal file
View file

@ -0,0 +1,18 @@
Copyright 2025 @lassulus, Germany
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,6 +1,6 @@
{
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
outputs = { nixpkgs, ... }@self: let
outputs = { nixpkgs, ... }: let
supportedArchitectures = [
"aarch64-darwin"
"aarch64-linux"
@ -11,12 +11,16 @@
packages = nixpkgs.lib.genAttrs supportedArchitectures (system: {
nix_templater = nixpkgs.legacyPackages.${system}.callPackage ./pkgs/nix_templater {};
});
legacyPackages = nixpkgs.lib.genAttrs supportedArchitectures (system: import ./lib.nix {
legacyPackages = nixpkgs.lib.genAttrs supportedArchitectures (system: let
pkgs = nixpkgs.legacyPackages.${system};
in import ./lib.nix {
inherit pkgs;
inherit (pkgs) lib;
nix_templater = packages.${system}.nix_templater;
});
checks = nixpkgs.lib.genAttrs supportedArchitectures (system: {
template = import ./tests/template.nix { inherit legacyPackages system nixpkgs; };
json = import ./tests/json.nix { inherit legacyPackages system nixpkgs; };
});
};
}

26
lib.nix
View file

@ -1,5 +1,11 @@
{ pkgs, nix_templater }:
{
{ pkgs, lib, nix_templater }:
let
escapeJson = {
"\"" = ''\"'';
"\\" = ''\\'';
};
in
rec {
# placeholder to be substituted with the content of a secret file
fileContents = file: {
outPath = "<${builtins.placeholder "nix_template"}${toString file}${builtins.placeholder "nix_template"}>";
@ -7,12 +13,12 @@
};
# make a template with placeholders
template_text = { name, text, outPath }:
templateText = { name, text, outPath, translations ? {} }:
pkgs.runCommand name {
textBeforeTemplate = text;
script = ''
#!/bin/sh
${nix_templater}/bin/nix_templater ${builtins.placeholder "out"}/template ${builtins.placeholder "nix_template"} "${outPath}"
${nix_templater}/bin/nix_templater ${builtins.placeholder "out"}/template ${builtins.placeholder "nix_template"} "${outPath}" '${lib.strings.toJSON translations}'
'';
passAsFile = [ "script" "textBeforeTemplate" ];
} ''
@ -21,4 +27,16 @@
cp $scriptPath $out/bin/${name}
chmod +x $out/bin/${name}
'';
templateGenerator = translations: generator: { name, value, outPath }: templateText {
inherit name outPath translations;
text = generator value;
};
templateJsonWith = options: templateGenerator escapeJson (lib.generators.toJSON options);
templateYamlWith = options: templateGenerator escapeJson (lib.generators.toYAML options); # just json
templateIniWith = options: templateGenerator escapeJson (lib.generators.toINI options);
templateJson = templateJsonWith { };
templateYaml = templateYamlWith { };
templateIni = templateIniWith { };
}

View file

@ -1,10 +1,12 @@
# replace occurrences of a magic string in a template file
from json import loads
import sys
from pathlib import Path
template_file = sys.argv[1]
magic_string = sys.argv[2]
outfile = sys.argv[3]
translations = loads(sys.argv[4]) if len(sys.argv) >= 4 else {}
if Path(outfile).exists():
print(f"{outfile} already exists, aborting")
@ -26,7 +28,7 @@ while True:
]
output += template_bytes[loc : loc + magic_start]
# TODO handle errors better here
output += Path(magic_file.decode()).read_bytes()
output += Path(magic_file.decode()).read_bytes().decode().translate(str.maketrans(translations)).encode()
loc = loc + magic_start + magic_end + len(magic_string) + 1
Path(outfile).write_bytes(output)

41
tests/json.nix Normal file
View file

@ -0,0 +1,41 @@
# test injecting a secret into a json template
{ legacyPackages, system, nixpkgs }:
let
hostPkgs = nixpkgs.legacyPackages.${system};
secret_file = hostPkgs.writeText "secret" "secret\\needing\"escaping";
in (nixpkgs.lib.nixos.runTest {
inherit hostPkgs;
name = "nix_templates";
nodes.machine = {pkgs, ...}: {
config = {
systemd.services.testservice = {
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
ExecStartPre = "${legacyPackages.${system}.templateJson {
name = "test";
value = {
foo = "text";
bar = legacyPackages.${system}.fileContents secret_file;
};
outPath = "./test";
}}/bin/test";
ExecStart = pkgs.writeScript "test_file_got_templates" ''
#!/bin/sh
cat ./test | grep -q 'secret'
'';
};
};
};
};
testScript = ''
start_all()
print(machine.execute("uname -a"))
machine.wait_for_unit("multi-user.target")
print(machine.succeed("cat /test"))
print(machine.succeed("cat /test | grep -q secret"))
print(machine.succeed("cat /test | ${hostPkgs.jq}/bin/jq"))
'';
})

View file

@ -1,4 +1,4 @@
# test injecting a secret into a template
# test injecting a secret into a text template
{ legacyPackages, system, nixpkgs }:
let
# this file would usually be outside of the store
@ -14,7 +14,7 @@ in (nixpkgs.lib.nixos.runTest {
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
ExecStartPre = "${legacyPackages.${system}.template_text {
ExecStartPre = "${legacyPackages.${system}.templateText {
name = "test";
text = ''
public text