Skip to main content

Templates and Inputs

Inputs make one recipe configurable without turning it into executable install logic. Templates substitute those typed values into files; runtime fields can use non-secret values where the manifest permits interpolation.

Declare inputs​

[inputs.server_name]
type = "string"
default = "Minecraft Server"
prompt = "Server name"
pattern = "^.{1,80}$"

[inputs.memory]
type = "string"
default = "4g"
pattern = "^[1-9][0-9]*[mMgG]$"

[inputs.port]
type = "integer"
default = 25565
min = 1
max = 65535

[inputs.eula]
type = "boolean"
required = true
prompt = "Accept the Minecraft EULA"

Input types are string, integer, and boolean. Defaults must have the same TOML scalar type. Integer min/max and string pattern validation run again when package values are resolved. pattern is a Python regular expression matched against the complete value.

prompt is the human-facing label shown by nesk info and the interactive install wizard. The wizard visits inputs not already supplied on the command line; Enter keeps a declared default or skips an optional value. Automation remains prompt-free and must supply every required value explicitly.

Supply values​

nesk plan --dir ./recipe \
--set eula=true \
--set server_name="My Server" \
--set memory=6g \
--set port=25566

--set KEY=VALUE is repeatable. Integer text is converted with Python's integer parser. Boolean text accepts only case-insensitive true or false. An unknown name, duplicate name across value/file forms, empty assignment, wrong type, failed regex, or out-of-range integer is an error.

The interactive install wizard also accepts yes/no for boolean inputs. This is only input syntax: --yes separately approves the displayed install plan and does not set a boolean recipe input such as eula.

Install/update values have this precedence:

  1. a --set or --set-file override;
  2. an existing non-secret instance value during update;
  3. the manifest default;
  4. missing—allowed only when the input is not required and nothing references it during rendering/runtime creation.

Successful install/update persists non-secret resolved values in instance state. start and restart combine those saved values with command-line overrides, but runtime overrides are not persisted and do not re-render files.

Secret inputs​

[inputs.rcon_password]
type = "string"
required = true
secret = true
prompt = "RCON password"

Supply a secret from a file, never a process argument:

nesk install ./recipe --dir ./instance \
--set-file rcon_password=./secrets/rcon-password.txt

The input file must be UTF-8 and at most 64 KiB. Luminesk removes one trailing LF or CRLF. Secret inputs:

  • cannot declare a default;
  • are rejected by --set and must use --set-file;
  • are excluded from persisted instance state and normal JSON output;
  • may be referenced only in rendered file content, not runtime image/argv, workdir, memory, mounts, ports, stop/restart fields, or checks;
  • cause the rendered file mode to be owner-only (0600, or 0700 when the file is explicitly executable).

The original secret still exists in the input file and rendered instance file; protect both. Because the value is not persisted, an interactive update asks for the file again when it must re-render secret-bearing content. Automation must repeat the appropriate --set-file option.

Top-level template tree​

Declare one recipe-relative directory:

template = "template"
template/
├── eula.txt.tmpl
├── welcome.txt.tmpl
└── defaults.yml
  • Directories and regular files keep their relative layout.
  • A filename ending in .tmpl is decoded as UTF-8, rendered, and installed without the suffix.
  • Other regular files are copied byte-for-byte.
  • Output ownership defaults to generated; [ownership] can override matching paths to preserve or data.
  • A collision with source/build/[[files]] output is rejected.

Template collection rejects symlinks, hardlinks, and special files. It is bounded to 4,096 regular files, 16 MiB per file, and 64 MiB total.

For template/welcome.txt.tmpl:

Welcome to ${input.server_name}.

For template/eula.txt.tmpl:

eula=${input.eula}

Boolean rendering is lowercase. Placeholders with no resolved value fail the package build; unknown text outside ${input.NAME} remains unchanged.

Rendering replaces the complete target file; it does not merge keys or lines into a file the server would otherwise generate. Never publish a partial server.properties, YAML, JSON, or similar server-owned configuration as a template. Omit that path and let the server create its complete defaults, or seed a complete upstream-compatible file when the recipe truly must own the initial contents.

Explicit [[files]] templates​

Use [[files]] when source and target names differ, when one source directory needs a single ownership mode, or when executable behavior is explicit:

[[files]]
source = "config/server.properties.in"
target = "server.properties"
mode = "preserve"
template = true

[[files]]
source = "scripts/healthcheck.sh.in"
target = "bin/healthcheck"
mode = "managed"
template = true
executable = true

With template = true, every regular source file is rendered as UTF-8. The source filename is not modified automatically—target is exact. A directory source is copied recursively below the target. Symlinks and special files are rejected.

Use one of the four ownership modes deliberately:

  • managed for recipe content replaced only while the installed digest is unchanged;
  • generated for rendered output with the same conflict protection;
  • preserve for a seed file whose existing user copy always wins;
  • data for user-owned directories/files. An absent data-mode source creates an empty target directory.

Executable files and reproducible modes​

Recipe host permissions are not copied into packages. Normal files are 0644, directories are 0755, and only a declared executable = true or matching [ownership].executable path adds the owner executable bit. Secret-bearing files remain owner-only. This keeps package metadata stable across Linux, macOS, and Windows recipe authors.

Input interpolation outside files​

Non-secret ${input.NAME} values can appear in runtime strings, argv elements, mount source/target, port values, and check fields. Port strings are stricter: they must consist only of one input reference in the manifest, then resolve to an integer from 1 through 65535.

[runtime]
image = "eclipse-temurin:21-jre"
command = ["java", "-Xmx${input.memory}", "-jar", "server.jar", "nogui"]
memory = "${input.memory}"

[[runtime.ports]]
name = "game"
host = "${input.port}"
container = "${input.port}"

Source provider options and package metadata are not template fields. Put version selection in the provider's own supported option instead of expecting generic interpolation.

Safe configuration workflow​

For server-generated files users will edit, omit the file from package inputs, set an explicit preserve policy, and include it in transaction backups:

[ownership]
preserve = ["server.properties"]
data = ["world", "plugins"]

[update]
backup = ["server.properties", "world", "plugins"]

After installation, start the server once, stop it, and edit the complete file it generated. If seeding a config is unavoidable, provide the complete file and use mode = "preserve"; a later recipe cannot silently merge new defaults into the operator's existing copy.

nesk plan --dir ./recipe --set eula=true
nesk install ./recipe --dir ./test-instance --set eula=true --dry-run
nesk diff --dir ./instance
nesk update --dir ./instance --dry-run

See Ownership and User Data for the exact update behavior.