mkdot
is a Python script that generates user/system configuration files ("dotfiles") from Jinja2 templates. It is designed to work equally-well as both an interactive "on-demand" script with useful stdout/stderr messages and a hands-off automation-ready script with filesystem logging.
- Optional file logging.
- "Dry Run" support for generating dotfiles without saving changes.
- Color output (with option to disable).
- Backup of existing dotfiles prior to writing changes.
- Default behavior configuration via environment variables.
- Ability to quickly revert breaking changes.
mkdot
runs the underlyingrsync
process with the--delete
flag when taking a backup of the output directory but without the--delete
flag when writing the newly-generated dotfiles to the output directory, unless the--delete
option is passed to the script.- Running
mkdot
with--dry-run
still writes the generated dotfiles to the specified or default working directory. This is so you can manually inspect the files afterward.
- The
mkdot
-provided Jinja functionrequire
does not currently validate elements of variables more than one element deep. In other words,require('foo.bar')
will work, butrequire('foo.bar.baz')
will not.
mkdot
is invoked with two required arguments: a template configuration (YAML) file, and a template source directory. With regards to this repo, the simplest example would be:
$ ./mkdot example/example.yaml example/templates
If a directory is supplied as the first argument, then mkdot
will automatically select a file called mkdot.yaml
underneath that directory, if it exists. Otherwise, it will select the file whose name (without the .yaml
extension) is closest to the hostname of the executing machine.
The following table describes the remaining optional arguments:
Argument(s) | Description |
---|---|
--block-end-string |
Specifies the string marking the end of a Jinja template block. |
--block-start-string |
Specifies the string marking the start of a Jinja template block. |
--command-end-string |
Specifies the string marking the end of a Jinja template comment. |
--comment-start-string |
Specifies the string marking the start of a Jinja template comment. |
--delete |
Specifies that the script should delete any files in the output directory that are not part of the generated dotfiles. + |
--dont-trim-blocks |
Specifies that the first newline character after a Jinja block should NOT be removed. |
-d , --dry-run |
Specifies that the script should only execute a dry-run, preventing the generated dotfiles from being copied from the working directory to the output directory. |
--exclude |
Specifies an additional list of files or directories relative to the specified output directory that should be preserved on write (if --delete is supplied). |
--fqdn |
Specifies that the given string should be used to set the provided fqdn and hostname Jinja variables, instead of the FQDN of the executing machine. |
-h , --help |
Displays help and usage information. |
-f , --log-file |
Specifies a log file to write to in addition to stdout/stderr. |
-l , --log-level |
Specifies the log level of the script. This option is ignored if --log-file is not specified. |
-m , --log-mode |
Specifies whether to append or overwrite the specified log file. This option is ignored if --log-file is not specified. |
--no-backup |
Specifies that the script should not perform a backup of the specified output directory prior to writing the generated dotfiles. |
--no-color |
Disables color output to stdout/stderr. |
-o , --output |
Specifies the output directory of the generated dotfiles. |
--revert |
Restores the contents of an existing backup directory relative to the specified output directory. If a path is provided alongside this flag, that location will instead be utilized as the restoration source. |
--rsync-executable |
Specifies a file path to the rsync executable utilized for transferring directories. |
-r , --run |
Specifies a command to run after the newly generated dotfiles are written to the output directory. This command is not run if --dry-run is specified. |
--variable-end-string |
Specifies the string marking the end of a Jinja template variable. |
--variable-start-string |
Specifies the string marking the start of a Jinja template variable. |
-w , --working-directory |
Specifies the working directory. |
+ Essentially this enables the --delete
flag of the underlying rsync process used to sync the working and output directories.
Each of the above options has the following set of corresponding value types and default values:
Argument(s) | Value Type / Possible Values | Default Value |
---|---|---|
--block-end-string |
Generic String | %} |
--block-start-string |
Generic String | {% |
--command-end-string |
Generic String | #} |
--comment-start-string |
Generic String | {# |
--delete |
||
-d , --dry-run |
||
--exclude |
Directory/File Paths | |
--fqdn |
Fully-Qualified Domain Name | |
-h , --help |
||
-f , --log-file |
File Path | |
-l , --log-level |
info or debug |
info |
-m , --log-mode |
append or overwrite |
append |
--no-backup |
||
--no-color |
||
-o , --output |
Directory Path | ~/.config |
--revert |
Directory Path (Optional) | |
--rsync-executable |
File Path | /usr/bin/rsync |
-r , --run |
Shell Command | |
--variable-end-string |
Generic String | }} |
--variable-start-string |
Generic String | {{ |
-w , --working-directory |
Directory Path | /tmp/mkdot |
The output of a typical run may look something like this on stderr/stdout:
~/projects/dotfiles/mkdot > mkdot yaml/mkdot-i3.yaml templates --no-color
:: Validating working environment...
--> Validating rsync executable path...
--> Validating template source directory...
--> Validating template configuration file...
--> Validating working directory...
:: Loading template configuration file...
--> Reading template configuration file...
--> Parsing template configuration file...
--> Validating template configuration...
:: Setting-up templating environment...
--> Initializing loader...
--> Initializing environment...
--> Initializing extensions...
:: Copying additional files...
--> misc/cursor.theme
--> x/exports.sh
--> misc/user-dirs.dirs
--> zsh/exports.zsh
--> zsh/prompt.zsh
--> zsh/options.zsh
--> zsh/aliases.zsh
--> scripts/wal-set.sh
--> scripts/scrot-full.sh
--> scripts/rofi-network.sh
--> scripts/rofi-exit.sh
--> scripts/rofi-run.sh
--> scripts/install-arch.sh
--> scripts/lock.sh
--> scripts/ssh-wrapper.sh
--> scripts/i3-restart-wrapper.sh
--> scripts/rofi-ssh.sh
--> scripts/setup-monitors.sh
--> scripts/rofi-lpass.sh
--> scripts/scrot-select.sh
--> scripts/polybar-start.sh
--> scripts/mkdot-apply.sh
--> scripts/i3-startup.sh
:: Translating templates...
--> rofi/config
--> trizen/trizen.conf
--> dunst/dunstrc
--> zsh/zshrc.sh
--> polybar/config
--> x/initrc.sh
--> x/resources.conf
--> i3/config
:: Finalizing configuration process...
--> Creating backup of existing dotfiles...
--> Writing dotfiles to output directory...
--> Creating symlinks...
The corresponding log file for the above run looks like this:
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.main] Started configuration process.
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.validate_environment] Validating working environment...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.parse_yaml_config] Loading template configuration file...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.setup_jinja] Setting-up templating environment...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying additional files...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/misc/cursor.theme" to "/tmp/mkdot/misc/cursor.theme"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/x/exports.sh" to "/tmp/mkdot/x/exports.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/misc/user-dirs.dirs" to "/tmp/mkdot/misc/user-dirs.dirs"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/zsh/exports.zsh" to "/tmp/mkdot/zsh/exports.zsh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/zsh/prompt.zsh" to "/tmp/mkdot/zsh/prompt.zsh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/zsh/options.zsh" to "/tmp/mkdot/zsh/options.zsh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/zsh/aliases.zsh" to "/tmp/mkdot/zsh/aliases.zsh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/scripts/wal-set.sh" to "/tmp/mkdot/scripts/wal-set.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/scripts/scrot-full.sh" to "/tmp/mkdot/scripts/scrot-full.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/scripts/rofi-network.sh" to "/tmp/mkdot/scripts/rofi-network.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/scripts/rofi-exit.sh" to "/tmp/mkdot/scripts/rofi-exit.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/scripts/rofi-run.sh" to "/tmp/mkdot/scripts/rofi-run.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/scripts/install-arch.sh" to "/tmp/mkdot/scripts/install-arch.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/scripts/lock.sh" to "/tmp/mkdot/scripts/lock.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/scripts/ssh-wrapper.sh" to "/tmp/mkdot/scripts/ssh-wrapper.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/scripts/i3-restart-wrapper.sh" to "/tmp/mkdot/scripts/i3-restart-wrapper.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/scripts/rofi-ssh.sh" to "/tmp/mkdot/scripts/rofi-ssh.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/scripts/setup-monitors.sh" to "/tmp/mkdot/scripts/setup-monitors.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/scripts/rofi-lpass.sh" to "/tmp/mkdot/scripts/rofi-lpass.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/scripts/scrot-select.sh" to "/tmp/mkdot/scripts/scrot-select.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/scripts/polybar-start.sh" to "/tmp/mkdot/scripts/polybar-start.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/scripts/mkdot-apply.sh" to "/tmp/mkdot/scripts/mkdot-apply.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.copy_files] Copying "templates/scripts/i3-startup.sh" to "/tmp/mkdot/scripts/i3-startup.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.translate_templates] Translating templates...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.translate_templates] Translating "templates/rofi/config.template" into "/tmp/mkdot/rofi/config"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.translate_templates] Translating "templates/trizen/trizen.template" into "/tmp/mkdot/trizen/trizen.conf"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.translate_templates] Translating "templates/dunst/dunstrc.template" into "/tmp/mkdot/dunst/dunstrc"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.translate_templates] Translating "templates/zsh/zshrc.template" into "/tmp/mkdot/zsh/zshrc.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.translate_templates] Translating "templates/polybar/config.template" into "/tmp/mkdot/polybar/config"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.translate_templates] Translating "templates/x/initrc.template" into "/tmp/mkdot/x/initrc.sh"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.translate_templates] Translating "templates/x/resources.template" into "/tmp/mkdot/x/resources.conf"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.translate_templates] Translating "templates/i3/config.template" into "/tmp/mkdot/i3/config"...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.write_output] Finalizing configuration process...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.write_output] Creating backup of existing dotfiles...
[INF] [12/04/2018 11:15:26 AM] [10013] [mkdot.write_output] Writing dotfiles to output directory...
[INF] [12/04/2018 11:15:27 AM] [10013] [mkdot.write_output] Creating symlinks...
[INF] [12/04/2018 11:15:27 AM] [10013] [mkdot.main] Configuration process complete.
Notice that the log file follows the following format:
[LOG_LEVEL] [TIMESTAMP] [PROCESS_ID] [MODULE.FUNCTION] MESSAGE
The script not only returns non-zero exit codes on fatal errors, but even broadly categorizes them:
Code | Description |
---|---|
0 | Script successfully ran, although perhaps with warnings. |
1 | Generic issue prior to the environment validation step (invalid arguments, import exceptions, etc). |
2 | Issue during the environment validation step. |
3 | Issue with loading, parsing, or validating the template configuration file. |
4 | Issue with instantiating or configuring the Jinja templating environment. |
5 | Issue with translating the source templates or saving them to the working directory. |
6 | Issue with performing the backup of the existing dotfiles or writing the new dotfiles to the output directory. |
7 | Issue occurring during a --revert operation. |
100 | Script was interrupted via CTRL+C or CTRL+D. |
The default behavior of the mkdot
script may also be configured via environment variables. Each environment variable has an associated command line argument, as described in the table below:
Environment Variable | Corresponding CLI Argument |
---|---|
MKDOT_BLOCK_END_STR |
--block-end-string |
MKDOT_BLOCK_START_STR |
--block-start-string |
MKDOT_COMMENT_END_STR |
--comment-end-string |
MKDOT_COMMENT_START_STR |
--comment-start-string |
MKDOT_EXCLUDE |
--exclude |
MKDOT_FQDN |
--fqdn |
MKDOT_LOG_FILE |
--log-file |
MKDOT_LOG_LVL |
--log-level |
MKDOT_LOG_MODE |
--log-mode |
MKDOT_OUTPUT |
--output |
MKDOT_RSYNC_PATH |
--rsync-executable |
MKDOT_RUN |
--run |
MKDOT_VAR_END_STR |
--variable-end-string |
MKDOT_VAR_START_STR |
--variable-start-string |
MKDOT_WORKING_DIR |
--working-directory |
As an example, if MKDOT_WORKING_DIR
was set to /tmp/foo
but -w /tmp/bar
was passed to mkdot
via command-line, then the script would use the value of /tmp/bar
for the working directory.
In the case that a falure occurs prior to the finalization step (in other words, a failure case that does not produce an exit code of 6
), it can be assumed that the existing dotfiles have not been corrupted and thus no restoration of a backup is necessary. However in the event that mkdot
encounters a critical issue during the finalization step, the user may quickly revert any changes by restoring the dedicated backup directory by executing:
$ mkdot --revert
In additon to restoring dotfiles from the dedicated backup directory, the --revert
option can also be specified in conjection with a specific source directory. The following example would restore dotfiles from /foo/bar/2018.backup
:
$ mkdot --revert /foo/bar/2018.backup
See TEMPLATING.md
within this repo for further information on designing dotfile templates to work with mkdot
.