Introduction
Comtrya is a permissively licensed Open Source tool that is built 100% in Rust. It allows you, as the user, to provision and configure your systems through the use of simple configuration files using the YAML or TOML formats.
The goals of comtrya are as follows:
- Run on any operating system
- Provide a simple YAML/TOML interface to, potentially, complex tasks
Comtrya's source code is available on GitHub.
Comparison to alternatives
Ansible
Ansible is a great tool task runner, but comes with a lot of modules that aren't really necessary for localhost provisioning and can be cumbersome to run individual tasks within a playbook.
SaltStack
SaltStack has been a favourite of mine (@rawkode) for many years, and while it's event system is a game changer for working with many devices - it's inability to display progress of large state runs makes it cumbersome to use for localhost provisioning.
Supported Systems
The following operating systems are officially supported by Comtrya:
- FreeBSD
- Linux
- macOS
- NetBSD
- Windows
If your operating system is not listed, file an issue on the projects GitHub and the team will see what needs to be done to add support.
Installation
There are several ways to install Comtrya on a system: cargo, shell script, provided binaries, or package managers. Comtrya can also be built from source.
Cargo installation
If your system has the Rust programming language tools such as cargo installed, cargo can be used to fetch the source and build a binary on your machine, placing it in location accessible by your path environment variable. First, ensure that the Rust programming language tool is installed by running the following:
cargo --version
You should see the version of cargo installed on your system printed out. If you get an error or nothing, please get the Rust tooling from the Rust Website if you want to build from source.
Once you have cargo and rustc on your system, you can fetch the sources and build with the following command:
cargo install comtrya
Shell Script
A shell script is provided to fetch a pre-compiled comtrya binary and install it onto your system (if your operating system is supported by the script). To do this, in your terminal or command prompt, run the following line:
curl -fsSL https://get.comtrya.dev | sh
Or, optionally, you can get a specific version of comtrya using the following one-liner:
curl -fsSL https://get.comtrya.dev | VERSION=v0.9.1 sh
Precompiled-binaries
Pre-compiled binaries are also included on our GitHub repository under our releases.
Package managers
Some package managers may provide comtrya. Check with yours. At the time of writing this, comtrya is available in the Arch User Repository (AUR), Homebrew and Ravenports. If you are interested in packaging comtrya for another ports or packaging system, feel free to reach out to the team by opening an issue on our GitHub for support.
Building from source
Building from source should be a straight forward task for anyone familiar with the Rust toolchian. It is recommended that you read through the cargo book and get familiar with it. Once you are, building is a matter of simply cloning our repository and compiling it. However, it is important to note that you may need to ensure you have the development libraries for openssl installed on your system. Check with your operating system and package manager what these packages are as they can often vary in naming between different systems.
CLI
Basic usage on local manifests
Comtrya works by running a manifest or set of manifests. Following are examples of running comtrya against manifests that are on the local machine:
# Run all manifests within your current directory
comtrya apply
# Run all manifests within your current directory and a specified configuration file
comtrya -c /path/to/Comtrya/yaml
# --manifests, or -m, will run a subset of your manifests
comtrya apply -m one,two,three
# Run all manifests within a specified directory
comtrya -d ./manifests apply
Please refer to the commands section for more information about the usage of apply
.
Basic usage on remote manifests
Comtrya also has the ability to run remote manifests, normally hosted in a git repository on github.
# Manifests in a Git repository
comtrya -d https://github.com/rawkode/rawkode apply
# Manifests in a Git repository with a branch and path
comtrya -d https://github.com/rawkode/rawkode#main:dotfiles apply
# Manifests in a Git repository with a branch and path and a subset selector
comtrya -d https://github.com/rawkode/rawkode#main:dotfiles apply -m dev.git
Help menu
Comtrya provides a help menu that can be shown by running the following command in your terminal:
comtrya -h
A tool to simplify reprovisioning a fresh OS. Installs packages and manages dotfiles.
Usage: comtrya [OPTIONS] <COMMAND>
Commands:
apply Apply manifests
status List manifests status (ALPHA)
version Print version information
contexts List available contexts
gen-completions Auto generate completions
help Print this message or the help of the given subcommand(s)
Options:
-d, --manifest-directory <MANIFEST_DIRECTORY>
-c, --config-path <CONFIG_PATH>
Specify a configuration path (if invalid Comtrya will exit)
--no-color
Disable color printing
-D, --defines <DEFINES>
-v...
Debug & tracing mode (-v, -vv)
-h, --help
Print help
-V, --version
Print versionA tool to simplify reprovisioning a fresh OS. Installs packages and manages dotfiles.
Auto generate completions
Shell completions for comtrya can be generated by the desired shell via gen-completions
subcommand.
comtrya gen-completions [SHELL]
for bash:
source <(comtrya gen-completions bash)
for fish:
comtrya gen-completions fish | source
Define variables via CLI
Comtrya offers the ability to set variables to use throughout manifests. Variables have normally been defined via the Comtrya config file named Comtrya.yaml
at the root directory of the manifests. However, variables can also be defined in the command line interface by using the defines
options.
comtrya -v -d ./ --defines foo=bar apply -m sample
In the sample, a variable is created in comtrya with the name foo
and is set to the value bar
. An action, like the sample below, can utilize this variable.
- action: command.run
command: echo
args:
- "{{ variables.foo }}"
Commands
Comtrya offers several commands for use. They can be listed in the terminal by running comtrya with the help command.
comtrya help
The most frequently used command s the apply command, which will apply the actions of provided manifests to the system.
Available Commands
Command | Description |
---|---|
apply | Apply manifests |
status | List manifest status |
version | Print version information |
contexts | List available contexts |
gen-completions | Auto generate completions |
help | Print out help information for using comtrya |
Apply
The apply command executes and runs the manifests. There are a few ways to do this.
The first option is to point comtrya to a directory of manifests and have it execute them all:
comtrya -d ./manifests apply
As shown, this is done with the -d
option, which tells comtrya the directory that contains the manifests to be applied.
The second option is to specify specific manifest(s) to be executed:
comtrya apply -m one,two,three
The -m
option is used to let comtrya know which manifests to apply. Note that the name of the manifest (i.e. one.yaml) is only the name and must not contain any path information or file extension (.yaml). So, /manifests/one
is not a valid input. Any manifests are expected to be located in the directory of the manifests you specified.
Suppose you have a directory manifests/
that contains the manifests one.yaml
and two.yaml
. You want to only execute one.yaml
. There are three ways to achieve this. You can simply specify the manifest if it's in the current working directory:
cd manifests/
comtrya apply -m one
Or you can specify the directory:
comtrya -d manifests/one.yaml apply
Alternatively a combination of the two is possible as well:
comtrya -d manifests/ apply -m one
Contexts
The contexts command is useful to see what comtrya knows about your system. This can be environment variables, included variables, information about the OS, user information and other variables. Below is an exmaple of the output.
env
COLORTERM DBUS_SESSION DESKTOP_SES DISPLAY DOTNET_BUND GDMSESSION
_BUS_ADDRESS SION LE_EXTRACT_
BASE_DIR
GNOME_DESKTO GNOME_SHELL_ GNOME_TERMI GNOME_TERMI GPG_AGENT_I GTK_IM_MODU
P_SESSION_ID SESSION_MODE NAL_SCREEN NAL_SERVICE NFO LE
GTK_MODULES HOME LANG LESSCLOSE LESSOPEN LOGNAME
LS_COLORS NVM_BIN NVM_CD_FLAG NVM_DIR NVM_INC OLDPWD
S
PATH PWD QT_ACCESSIB QT_IM_MODUL SESSION_MAN SHELL
ILITY E AGER
SHLVL SSH_AGENT_LA SSH_AUTH_SO SWIFTLY_BIN SWIFTLY_HOM SYSTEMD_EXE
UNCHER CK _DIR E_DIR C_PID
TERM TEXTDOMAIN USER USERNAME VTE_VERSION WINDOWPATH
XAUTHORITY XDG_CONFIG_D XDG_CURRENT XDG_DATA_DI XDG_MENU_PR XDG_RUNTIME
IRS _DESKTOP RS EFIX _DIR
XDG_SESSION_ XDG_SESSION_ XDG_SESSION XMODIFIERS _
CLASS DESKTOP _TYPE
include_variables
<empty>
os
bitness
codename
distribution
edition
family
hostname
name
version
user
config_dir
data_dir
data_local_dir
document_dir
home_dir
id
name
username
variables
<empty>
You can also view the values that these contexts have by passing in a show-values
option as demonstrated below:
comtrya contexts --show-values
Status
The status command provides an overview of manifests.
Manifest | Count of Actions |
---|---|
kubectl.krew | 3 |
kubectl.kubesess | 3 |
kdash.kdash | 1 |
Manifests
Comtrya provisions systems and performs configuration using a single or set of manifest files. These files are defined in either YAML or TOML syntax. Each manifest files is composed of actions and dependencies. To break it down even further, each action is defined as an atom or a set of atoms. An action can be something as simple as echoing text out on a terminal.
A dependency defines a relationship between manifests and actions. For instance, in order to configure neovim on a new system that is being provisioned, we might first want comtrya to ensure that neovim is installed. So we may define a dependency on an action to use the system's native package manager to install neovim before placing any configuration files we need for neovim.
Example of a YAML manifest
actions:
- action: command.run
command: echo
args:
- hi
Example of TOML manifest
[[actions]]
action = "command.run"
command = "echo"
args = [ "hi" ]
Actions
As mentioned under manifests, actions are used to dictate an action that needs to be done to configuration a system. The following is an example of a simple action:
actions:
# Action ID can be command.run or cmd.run
- action: command.run
command: echo
args:
- hi
The above does the following: It defines a list of actions (with only one element). The action defined is the for command.run
, which in this case runs the echo command, providing the argument of 'hi.' The result is something equivalent to running the following command line on a Unix-based system:
echo hi
Groups of actions provided
Comtrya provides multiple actions which are broken down into groups with the actions being apart of a larger group:
Git actions are not avaible for 0.8.8 through 0.9.0, see action documentation for me information
Binary
- binary.github
binary.github
This action will grab a binary from GitHub and place it in the target directory.
Key | Type | Optional | Description |
---|---|---|---|
action | string | no | binary.github |
name | string | no | name of binary locally after download |
directory | string | no | directory to save the binary locally |
repository | string | no | Github repository |
version | string | no | version/tag name |
Example
- action: binary.github
name: comtrya
directory: /usr/local/bin
repository: comtrya/comtrya
version: v0.8.7
Commands
- command.run
command.run
This is the most basic, but yet powerful action as it can be used to run anything needed that is not directly supported in comtrya by an action. An alias exists, allowing you to abbreviate command
as cmd
.
Key | Type | Optional | Description |
---|---|---|---|
action | string | no | command.run |
command | string | no | command to run |
args | string | yes | argument passed |
dir | string | yes | actual working directory |
privileged | bool | yes | elevate privileges when executing |
env | HashMap<string, string> | yes | key and values for scoped environment variables |
Scoped environment variables
Sometimes, environment variables are needed to run a command or set of commands. As of v0.9.1, Comtrya will has the ability to inject environment variables for the scope of a single command.run
action. An initializer will run prior to the action to inject the environment variables, then after the command run finished, a finalizer will remove those from the environment. In the manifest, the environment is implemented as a hash map of keys and values. Multiple environment variables are supported.
Example
- action: command.run
dir: .
command: echo
args:
- Hello world
# we should see the GOBIN set in the go env output now
- action: command.run
command: go
args:
- env
env:
GOBIN: /Users/test
# we should see that GOBIN is no longer set, showing that the variable has been cleaned up
- action: command.run
command: go
args:
- env
Files and Directories
- file.copy
- file.download
- file.link
- file.remove
- file.unarchive
- directory.copy
Note
The following commands expect the from
/source
to point to files/directories which are themselves under a "files" directory.
This is a restriction so that comtrya knows not to parse any .yaml file e.g. a config for a different tool as a manifest.
To see how it works check the examples
- file.link
- file.copy
- directory.copy
file.copy
Action used to copy a file from one location to another.
Key | Type | Optional | Description |
---|---|---|---|
action | string | no | file.copy |
from | string | no | source file |
to | string | no | destination file |
template | boolean | yes | renders files using context providers |
default: false | |||
chmod | integer | yes | octal permissions |
owned_by_user | string | yes | user for chown |
owned_by_group | string | yes | group for chown |
Examples
# Simple file copy
- action: file.copy
from: procs-config.toml
to: "{{ user.config_dir }}/procs/config.toml"
# With template and permissions
- action: file.copy
from: managed_file
to: /root/file
template: true
chmod: 644
# With encrypted file
- action: file.copy
from: encrypted-file
to: /tmp/some-decrypted-file
passphrase: "1KZ2EXDHSQKZFQP43JK2LPXUFZ8D365CM5WQXRSH97U7N9WKRVFKS0TCS30"
# file copy with chown on unix systems
- action: file.copy
from: procs-config.toml
to: "{{ user.config_dir }}/procs/config.toml"
owned_by_user: test
owned_by_group: test
Note: utilizing chown functionality will require running comtrya as root. Also, both a user and a group need to be specified.
file.chown
This action will change the user and group owner of a file.
Note: In order to utilize this, must run comtrya as root. Also both a user and group need to be specified.
Key | Type | Optional | Description |
---|---|---|---|
action | string | no | file.chown |
path | string | no | file to change ownership on |
user | string | no | user to specify as file owner |
group | string | no | group to specify as file owner |
Examples
actions:
- action: file.chown
path: ./files/some-file
user: test
group: test
file.download
This action will download a file.
Key | Type | Optional | Description |
---|---|---|---|
action | string | no | file.download |
from | source | string | no |
to | target | string | no |
owned_by_user | string | yes | user for chown |
owned_by_group | string | yes | group for chown |
An alias also exists such that source
can be used in lieu of from
and target
can be used in lieu of to
.
Example
actions:
# This will be rendered with contexts
- action: file.download
from: https://google.com/robots.txt
to: /tmp/google-robots.txt
# This whill also run a chown step on downloaded file
- action: file.download
from: https://google.com/robots.txt
to: /tmp/google-robots.txt
owned_by_user: nobody
owned_by_group: nobody
Note: utilizing chown functionality will require running comtrya as root. Also, both a user and a group need to be specified.
file.link
Create a symlink for files. This action can be used to symlink a single file or files in a directory.
Key | Type | Optional | Description |
---|---|---|---|
action | string | no | file.link |
from | string | no | symlink location |
to | string | no | symlink points to |
walk dir | boolean | yes | Walk diles in directory |
default: false | |||
source | string | yes | Used in conjunction with walk dir |
in liue of from |
Example
# Symlink for a single file
- action: file.link
from: /root/symlink
to: managed_file
# Symlink for all files in a directory
- action: file.link
source: walker
target: /tml/walker-123
walk_dir: true
file.remove
Removes a file.
Key | Type | Optional | Description |
---|---|---|---|
action | string | no | file.remove |
target | string | no | file to be removed |
Example
- action: file.remove
target: /tmp/some-file-rendered
file.unarchive
This action provides the ability to unarchive a tar.gz
file.
Key | Type | Optional | Description |
---|---|---|---|
action | string | no | file.unarchive |
from | string | no | full path to archive |
to | string | no | destination of unarchived contents |
force | bool | yes | force the unarchiving (defaults true) |
Example
actions:
- action: file.download
from: https://github.com/comtrya/comtrya/archive/refs/tags/v0.9.0.tar.gz
to: /tmp/comtrya
- action: file.unarchive
from: /tmp/comtrya
to: /tmp/
directory.copy
Copies a directory on the filesystem to another location.
Key | Type | Optional | Description |
---|---|---|---|
action | string | no | directory.copy |
from | string | no | source directory |
to | string | no | destination directory |
Example
- action: directory.copy
from: managed_directory
to: /root/location
Git
- git.clone
Use notice
This functionality was removed in 0.8.8. However, it is back as of version 0.9.1. The older implementation had issues with openssl that caused some headaches. However, as of version 0.9.1, a new implementation will be available utilizing gix as the backend for git. There are some breaking changes.
git.clone [0.9.1 and later]
Perform a git clone on a repository from GitHub.
Key | Type | Optional | Description |
---|---|---|---|
action | string | no | git.clone |
repo_url | string | no | repository to clone |
directory | string | no | directory to clone to |
Example
actions:
- action: git.clone
repo_url: https://github.com/comtrya/comtrya
directory: /Users/test/Testing/comtrya/
git.clone [0.8.7 and prior]
Perform a git clone on a repository from GitHub.
Key | Type | Optional | Description |
---|---|---|---|
action | string | no | git.clone |
repository | string | no | repository to clone |
directory | string | no | directory to clone to |
Example
- action: git.clone
repository: comtrya/comtrya
directory: {{ user.home_dir }}/Code/src/
Group
- group.add
Support
Not all systems are currently supported. The following is a list of group providers:
- FreeBSD
- Linux
- macOS
If your OS of choice is not listed, feel free to either contribute support by opening a pull request or requesting that support be added in the repository's issue tracker.
group.add
Adds a group to the system.
Key | Type | Optional | Description |
---|---|---|---|
action | string | no | group.add |
group_name | string | no | name of group to add |
Example
actions:
- action: group.add
group_name: testgroup
macOS
- macos.default
macos.default
Key | Type | Optional | Description |
---|---|---|---|
action | string | no | macos.default |
domain | string | no | Domain: defaults or domains or https://macos-defaults.com |
key | string | no | which key to change |
kind | string | no | value type |
value | string | no | value |
Example
- action: macos.default
domain: com.apple.dock
key: orientation
kind: string
value: left
- action: macos.default
domain: com.apple.screencapture
key: include-date
kind: bool
value: "false"
- action: macos.default
domain: NSGlobalDomain
key: "NSTableViewDefaultSizeMode"
kind: int
value: "1"
Packages
- package.install
Package Providers
Packages is a group of actions that utilize the local system's package manager, however it is a bit special. Unlike some other actions, packages can also contain providers. Some operating systems may have multiple package managers available and providers allow the user to choose which package manager to use. For example, macOS will automatically utilize homebrew as the default package manager, but this default can be overridden for another package manager on the system such as pkgin, which also supports macOS, or macports.
Supported package providers
Not all package managers are supported. This is a list of currently supported package providers:
Provider | OS |
---|---|
pacman/yay | Arch |
paru | Arch |
apt | Debian/Ubuntu |
pkg | FreeBSD |
pkgin | NetBSD (Multiple) |
brew | macOS |
winget | Windows |
xbps | Void Linux |
zypper | OpenSUSE |
macports | macOS |
dnf | Fedora |
snapcraft | Linux |
If you would like support to be added for a package provider, feel free to contribute the support or request support in the repository issue tracker.
Important note on homebrew and macOS
Some package manager providers can implement a bootstrap
method that will automatically configure the package manager on the system if it is not part of the default installation. This is the case with macOS. Comtrya can automatically install homebrew
to a macOS system and will do so if a manifest specifies a package.install
action and does not overridfe the macOS default of homebrew.
package.install
Key | Type | Optional | Description |
---|---|---|---|
action | string | no | package.install |
name | string | no | name of target package |
list | list | yes | list of multiple packages |
provider | string | yes | Specify package provider |
repository | string | yes | specific repository for a provider and package |
file | bool | yes | Specify that package is a local package on the file system. |
Default value is false |
Example
# Install package using default provider
- action: package.install
name: curl
# Install a list of packages using default provider
- action: package.install
list:
- curl
- wget
# Install a package using a specific package provider
- action: package install
name: curl
provider: pkgin
# Install a package specifying a repository
- action: package.install
name: blox
provider: homebrew
repository: cueblox/tap
Local package install support
Some package providers allow for installing a package from the local file system. An example of this would be .pkg
files that can be installed using FreeBSD's package manager pkg
. As of this time, it requires that the file property be set in the action's definition.
List of supported package providers:
- pkg (FreeBSD)
- aptitude (debian/ubuntu)
If you would like to have this feature supported on another package provider, please open an issue at the comtrya GitHub repository.
Example
- action: package.install
name: /some/path/to/file/nano-8.1.pkg
file: true
User
- user.add
- user.group
user.add
Adds a user to the system.
Key | Type | Ooptional | Description |
---|---|---|---|
action | string | no | user.add |
fullname | string | no | full name of user |
home_dir | string | no | home directory of user |
username | string | no | username of user |
shell | string | yes | shell for user |
group | list | yes | groups to add new user to |
Example
- action: user.add
fullname: testuser
home_dir: /home/test
username: test
shell: sh
# Add user while also adding to groups
- action: user.add
fullname: testuser
home_dir: /home/test
username: test
shell: sh
group:
- testgroup
user.group
Adds an already created user to a group.
Key | Type | Optional | Description |
---|---|---|---|
action | string | no | user.group |
username | string | no | username of user to add to group |
group | list | no | groups to add user to |
Example
- action: user.group
username: test
group:
- wheel
Privilege Escalation
Escalating actions
Some actions may be run either privileged or unprivileged. For those unfamiliar, this means for example utilizing sudo or running something as admin. A common action this may be required for is command.run. Perhaps you wish to run a command that comtrya does not directly supply an action for, but that command requires elevated privileges to make changes to the system. Some actions allow that to be specified. Here is an example making use of command.run
:
- action: command.run
command: whoami
sudo: true
Comtrya knows two keywords for escalating privilege. In older versions, it had to be done using sudo, however sudo is a term typically associated with a specific application and has little meaning to a Windows user for example. Even on Unix-like systems, there are alternatives available. Comtrya's architecture allows for using other providers for privilege escalation. A more generic way to write the above action would be to use privileged
in lieu of sudo
.
- action: command.run
command: whoami
sudo: true
privileged: true
Privilege escalation providers
Comtrya, as of version 0.9.0 and later, supports different providers for privilege escalation. Not all operating systems may support sudo and some users prefer different programs, such as OpenBSD's doas which is available on multiple platforms or run0 which is part of systemd in versions 256 and newer.
In order to utilize different privilege providers, you must have a Comtrya.yaml
file which contains configuration for Comtrya. Here is an example:
privilege: doas
variables:
test: "one"
The privilege
specifies which provider. Below are the providers and relevant values to be set for privilege
:
Provider | Value |
---|---|
Sudo | sudo |
Doas | doas |
Run0 | run0 |
As a note, Comtrya will always fall back to utilizing sudo. An example is when an action is being executed, but no privilege escalation provider is specified.
Dependencies
In comtrya, manifests can depend on other manifests. This is helpful when you need to ensure a set of actions run before another because they depend on them in some way. This is done by specifying what manifests a manifest depends on.
Suppose we have a manifest for creating users and another manifest for creating groups. We may want to ensure that the groups are created before adding users to a group. In this examples, we have two files; users.yaml
and groups.yaml
. To ensure that groups.yaml runs before users.yaml, we write our groups.yaml as we normally would.
groups.yaml
actions:
- action: group.add
group_name: testgroup
users.yaml
depends:
- groups
actions:
- action: user.add
fullename: testuser
home_dir: /home/test
username: test
shell: sh
group:
- testgroup
As shown, at the top of the users.yaml
file, depends
takes a lists of manifests that this manifest depends on.
Variants
All comtrya actions support the concept of variants. Variants allow you to modify how the action will be executed backed on contexts.
A command example of a variant is a variant based on the operating system. Suppose you have an action that is going to differ slightly between a linux, windows, and macOS system. The following is an example of that.
actions:
# This action has a "default" execution for when the variants don't overlay,
# as it does not provide its own "where"
- action: command.run
command: echo
args:
- hello, world!
variants:
- where: os.name == "linux"
command: reboot
- where: os.name == "macos"
command: echo
args: ["Hello", "macOS"]
- where: user.username == "rawkode"
command: echo
args: ["Hello", "rawkode!"]
Variants can can allow targeting an OS family or a specific distributions.
actions:
- action: command.run
command: echo
args:
- hello, vanilla Linux!
variants:
- where: os.family == "unix"
command: echo
args: ["Hi,", "Unix"]
- where: os.distribution == "Ubuntu"
command: echo
args: ["Hi,", "Ubuntu"]
- where: os.bitness == "64-bit"
command: echo
args: ["Hi,", "64 bit!"]
Lastly, the where
clause can be used to selectively skip or run tasks:
actions:
- action: command.run
where: os.name == "linux"
command: echo
args:
- Hello Linux