Manifest
A manifest file, named dojang.toml, declares that the directory in which it resides is a repository for config files managed by Dojang, and sets up how those config files should be applied on the actual machine.
It is in TOML format, as you might guess from the extension, and is divided
into seven main sections: vars, dirs, files, monikers, ignores,
codec-backends, and hooks.
This document assumes that the reader knows the basic syntax of TOML.
Repository identity
The top-level repository-id field is a UUID created by dojang init or
dojang migrate:
repository-id = "123e4567-e89b-42d3-a456-426614174000"
This identity travels with the repository and remains unchanged when its checkout is moved or renamed. Do not edit it or copy it into another repository. Dojang uses it to isolate the repository's machine state on each device.
Codec backends
The optional codec-backends table declares commands used by the built-in
encrypted and secret-template codecs. Each key is a manifest-local backend
name:
[codec-backends.vault]
command = "$HOME/.local/bin/dojang-vault"
version = "2026-07"
timeout-seconds = 45
options = { profile = "work", strict = true }
command is a file path expression and must expand to an absolute executable
path. version is a nonempty identity supplied by the backend operator;
change it whenever backend behavior or key material changes. The optional
timeout-seconds field ranges from 1 to 300 and defaults to 30. options
accepts codec configuration values but must not contain secrets because it is
sent in the protocol header and may affect diagnostic identities.
Dojang invokes a backend without a shell, arguments, or inherited environment. The working directory is the repository root. Sensitive codec backends are not available on Windows in this release. See the codec backend protocol for the security boundary and recovery procedure.
Moniker
A moniker is the name given to the criteria for an environment. How the moniker is named is up to you, but it should be easy to recognize. For example, below are some of the most common and simple moniker definitions:
[monikers.windows]
os = "windows"
[monikers.posix]
os = ["linux", "macos"]
These monikers allow you to conditionally apply config files based on the environment. For example, if a user has an older Intel MacBook Pro and a newer Apple silicon MacBook Pro, they would be able to define the monikers like below:
[monikers.mac]
os = "macos"
[monikers.apple_silicon_mac]
any = ["mac"]
arch = "aarch64"
[monikers.intel_mac]
any = ["mac"]
arch = "x86_64"
In the example above, the any field is used to refer to another moniker
prerequisite. It is satisfied if any of the monitors in the list are satisfied.
If you want a predicate that requires all of the listed monitors to be
satisfied, you can use the all field.
If you have more complex criteria for the environment, you can use
the environment predicate syntax in the
when field. In fact, the above example can be rewritten with the environment
predicate syntax as shown below and it will work the same:
[monikers.mac]
when = "os = macos"
[monikers.apple_silicon_mac]
when = "moniker = mac && arch = aarch64"
[monikers.intel_mac]
when = "moniker = mac && arch = x86_64"
For a detailed description of the environment predicate syntax, see the corresponding documentation.
Fields that can be used when defining a moniker
-
os(string or array of strings): The operating system of the environment. If it is an array, it is satisfied if any of the operating systems in the array are satisfied. The OS identifiers are listed in the List of recognized operating systems section. -
arch(string or array of strings): The processor architecture of the environment. If it is an array, it is satisfied if any of the architectures in the array are satisfied. The architecture identifiers are listed in the List of recognized processor architectures section. -
kernel(string or array of strings): The kernel name of the environment. If it is an array, it is satisfied if any of the kernels in the array are satisfied. See also Kernel recognition section. -
kernel-release(string or array of strings): The kernel version of the environment. If it is an array, it is satisfied if any of the kernel versions in the array are satisfied. See also Kernel recognition section. -
any(array of strings): Other moniker names. It is satisfied if any of the monikers in the array are satisfied. There must be at least one element. -
all(array of strings): Other moniker names. It is satisfied if all of the monikers in the array are satisfied. There must be at least one element. -
when(string): Criteria written in the environment predicate syntax. It is satisfied if the criteria are satisfied. If used with the fields above, all criteria together must be satisfied.
Manifest variables
The optional vars section declares reusable values for
file path expressions. A compact declaration is an unconditional value:
[vars]
CONFIG_HOME = "${XDG_CONFIG_HOME:-$HOME/.config}"
CACHE_HOME = "$CONFIG_HOME/cache"
Names are case-sensitive and must match [A-Za-z_][A-Za-z0-9_]*. References
use the same $NAME or ${NAME} syntax as inherited environment variables.
Declarations may refer to one another in any table order. A reference cycle
is an error, and Dojang reports the cycle instead of applying any files.
Use an ordered array of branch tables when a value depends on the environment:
[vars]
TOOLS_HOME = [
{ when = "fact.class = work", value = "$HOME/work/tools" },
{ moniker = "windows", value = "$UserProfile/tools" },
{ when = "always", value = "$HOME/tools" },
]
Each branch must contain value and exactly one of moniker or when.
Branches are tried in declaration order, and the first match wins. If no
branch matches, lookup falls through to an inherited environment variable of
the same name. A selected empty string is different: it is a defined empty
value and shadows the inherited variable.
Manifest variables take precedence over inherited environment variables.
Machine facts and other environment predicates select branches; they are not
automatically exposed as variable values. dojang init asks for custom facts
referenced by reachable variable branches. Variable values are configuration,
not secrets: Dojang avoids storing their plaintext in route records and hook
fingerprints, but processes and diagnostics can still observe expanded paths.
Routing of directories and files
Routing is for applying config files to different paths depending on the environment. For example, the home directory is different on all three platforms Linux, macOS, and Windows, so it can be routed as follows:
[dirs.home_directory]
linux = "/home/$USER"
mac = "/Users/$USER"
win = "C:\\Users\\$UserName"
[monikers.linux]
os = "linux"
[monikers.mac]
os = "macos"
[monikers.win]
os = "windows"
Syntax like $USER or $UserName will be replaced with the value of
the corresponding environment variable, which is explained in more detail
in the document for the file path expression.
Of course, the above example is artificially crafted for illustrative purposes, and in practice we would do something simpler and more accurate like below:
[dirs.home_directory]
posix = "$HOME"
windows = "$UserProfile"
[monikers.posix]
os = ["linux", "macos"]
[monikers.windows]
os = ["windows"]
This compact route syntax maps each moniker directly to a path. It remains the shortest form when every branch uses a different moniker. To use an environment predicate directly, preserve repeated conditions, or mix both forms in one route, write the route as an ordered array of branch tables:
[[files.".bashrc"]]
moniker = "posix"
path = "$HOME/.bashrc"
[[files.".bashrc"]]
when = "os = windows && arch = aarch64"
path = "$UserProfile/.bashrc"
[[files.".bashrc"]]
when = "os = android"
Each detailed branch must have exactly one of moniker or when. A
moniker value must name a moniker defined in the same manifest, while when
accepts an environment predicate. The path
field is optional. Omitting it creates a null route for that condition.
Writing path = "" instead specifies an explicit empty path expression.
Dojang preserves detailed branches in their route order, including repeated
conditions. Branches are tried in that order, and the first matching branch
determines the destination. Compact routes continue to use predicate
specificity to determine priority.
Destination modes and kinds
A detailed branch may also declare metadata for its destination through the
optional mode and kind fields. Both require a path, so a null route
cannot carry metadata. Compact routes cannot declare metadata; use the
detailed form instead.
The mode field declares a portable permission intent for the destination
from a small closed vocabulary:
mode |
POSIX file | POSIX directory | Windows |
|---|---|---|---|
"default" (omitted) |
unmanaged | unmanaged | unmanaged |
"private" |
0600 |
0700 |
not enforceable |
"executable" |
0755 |
not applicable | not enforceable |
"private-executable" |
0700 |
not applicable | not enforceable |
"read-only" |
0444 |
0555 |
read-only attribute |
[[files."id_ed25519"]]
moniker = "posix"
path = "~/.ssh/id_ed25519"
mode = "private"
The manifest is authoritative for declared modes: dojang status reports a
destination whose permissions do not satisfy its declaration, and
dojang apply reconciles the destination toward the declared mode even when
its contents are unchanged. A declared mode is a desired final state rather
than a precondition, so applying updates to a read-only destination widens
it temporarily and narrows it back afterwards. Windows can only enforce the
read-only distinction; other declarations produce a warning there instead
of silently claiming success. Executable modes are meaningless for directory
routes and are rejected.
The kind field declares whether the destination is an ordinary copied
entry ("copy", the default) or a deployment link ("symlink"): a
symbolic link at the destination pointing back at the absolute path of the
route's source file in the repository checkout.
[[files.".vimrc"]]
moniker = "posix"
path = "~/.vimrc"
kind = "symlink"
A deployment link is a one-way projection of the repository source.
dojang apply creates a missing link, repairs one whose target no longer
matches the source (for example after the checkout moved), and refuses to
replace an existing regular file or directory unless --force is given.
dojang reflect never reflects a deployment link, even with --force or
when a path under a linked directory is named explicitly. A symbolic link
carries no portable permissions of its own, so a kind = "symlink" branch
cannot declare a non-default mode. Whether a link is created as a file
link or a directory link on Windows follows from the files or dirs
table declaring the route; creating symbolic links on Windows requires
Developer Mode or administrator privileges.
Route codecs
A detailed branch can set codec to transform a regular source file before
deployment. The string form names an unconfigured codec; the object form has
name and config fields. The default identity codec preserves bytes, so
existing compact and detailed routes keep their previous behavior. Codec
metadata requires path, and a deployment link accepts only identity.
The built-in template codec has no configuration and renders UTF-8 source
with manifest variables under vars and machine facts under facts.
encrypted, encrypted-re-add, and secret-template use a named
codec-backends declaration and require mode = "private" or
mode = "private-executable".
See route codecs for configuration values, caching, dry-run behavior, and reflection policies.
A route can be named like home_directory in the example above,
which actually refers to the path where the source of config files are located
within the repository. If it says home_directory, it refers to
home_directory/ in the repository, which is the directory where the manifest
file is located. So if your repository has files like the following:
- dojang.manifest
- home_directory/
- .bash_profile
- .inputrc
When applied to Linux, the files are copied to the following paths:
- /home/$USER/.bash_profile
- /home/$USER/.inputrc
Similarly, on Windows, they would look like below:
- C:\Users\%USERNAME%\.bash_profile
- C:\USers\%USERNAME%\.inputrc
In practice, more complex routing is possible, see the relevant documentation for a detailed explanation of routing.
List of files to ignore
There are usually a lot of unrelated files in the target path,
and if you try to use the command dojang status while leaving them untouched,
the terminal will be full of unrelated changes, making it difficult to actually
use. To avoid this, you can list files to ignore in the target path.
The files to ignore are listed inside the ignores section, whose name must
match the source directory you defined in the dirs section. On the other hand,
the files specified in the files section cannot be ignored as they are single
files. Each item in the list is described with a glob-like pattern syntax.
E.g.:
[dirs.HOME]
posix = "$HOME"
windows = "$UserProfile"
[ignores]
HOME = [
".config",
".DS_Store",
]
Note that even if a file is on your ignore list, it will not be ignored
if it has already been included in the repository. You can also use this
behavior to manage only files that have been added to the repository,
while ignoring everything else (*).
Hooks
Hooks run custom scripts before and after apply, reflect,
diff, status, edit, and unmanage. Each command has a pre-* and
successful post-* event. apply also retains its first-apply events. Hooks
can run always, once per repository and machine, or on-change with an
explicit revision key.
[[hooks.pre-apply]]
command = "/bin/echo"
args = ["Applying config files..."]
[[hooks.post-apply]]
id = "reload-service"
policy = "on-change"
change-key = "service-config-v2"
command = "/usr/bin/systemctl"
args = ["--user", "restart", "my-service"]
when = "os = linux"
For a detailed description of hooks, see the hooks documentation.