Introduction
Comtrya is a tool that is built 100% in Rust. It's goal is to allow you, as the user, to provision and configure your systems using through the use of simple configuration files in the form of YAML files or TOML files.
The goals of comtrya are as follows:
- Run on any operating system
- Provide a simple YAML/TOML interface to, potentially, complex tasks
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 for Comtrya. 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.
- Windows
- macOS
- Linux
- FreeBSD
- NetBSD
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. 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 if this is your perferred path from the Rust Website.
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. 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.0 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 your package manager. As of this time, comtrya is provided both by Arch in the AUR and Ravenports. If you are interested in packaging comtrya for another package manager, 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 go through the cargo book and have some familiarity. Outside of that, it is simply cloning our repository and building. 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. The 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>
--no-color Disable color printing
-v... Debug & tracing mode (-v, -vv)
-h, --help Print help
-V, --version Print version
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
Commands
Comtrya offer several commands for use. They can be listed in the terminal by running comtrya with the help command.
comtrya help
The primary command of use will be 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 will execute and run the manifests. There are a few ways to do this.
First, point it to a directory of manifests and have comtrya execute them all.
comtrya -d ./manifests apply
As shown, this is achieved with the -d
option, which tells comtrya the directory that houses the manifests to be executed.
Second, specify specific manifest(s) to be executed.
comtrya apply -m one,two,three
The -m
option is used to tell comtrya the specific manifests to run. Note that the name of the manifest (i.e. one.yaml)
is only the name of the manifest and does not contain any path information or file extension (.yaml). So, /manifests/one
is not a valid input.
So it is expected to be located in the directory of the manifests you are specifying to run.
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.
cd manifests/
comtrya apply -m one
Or
comtrya -d manifests/one.yaml apply
Or, the third and final way is a combination of the two.
comtrya -d manifests/ apply -m one
Contexts
The contexts command is useful to see what comtrya knows about. 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
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 the 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. The action defined in a list of actions is the action for command.run
, which runs the echo command, providing the argument of 'hi.' This results in something equivalent to running the following 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 a 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.
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 offer 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 step as ran, 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 | string | no | source location |
to | string | no | destination file |
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. If the system of your 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.
- FreeBSD
- Linux
- macOS
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 systems may have multiple package managers that may work on the system and this allows the user choice for what package manager on their system they use. As an 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. 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.
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 |
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. This is most applicable 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 the ability to install a package from the local file system. An example of this would be .pkg
files that can be utilized with 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.
Example
- action: package.instal
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 would be by utilizing sudo or
running something as admin. A common action this is utilized with is command.run. Perhaps you, as the user,
wish to run a command that comtrya does not directly supply an action for, but that command required some form of
privilege escalation, some actions allow that to be specified. Here is an example utilizing command.run
.
- action: command.run
command: whoami
sudo: true
Comtrya allows two terms for escalating privilege. Originally, it had to be done using sudo, however sudo is a term
typically tied to a specific application and has little meaning to a Windows user as an example. On unix-type systems,
there are also alternatives. Comtrya's architecture also allows for the ability to implement multiple 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, will support different providers for privilege escalation. Not all operating systems may support sudo or there is a preference for a different program to be used to escalate privileges, such as OpenBSD's doas which has a port that runs on multiple platforms or run0 which is now a part of systemd starting with version 256 and newer.
In order to utilize different privilege providers, you must have a Comtrya.yaml
file which contains configuration for
Comtrya. Below 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