Introduction

comtrya

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

CommandDescription
applyApply manifests
statusList manifest status
versionPrint version information
contextsList available contexts
gen-completionsAuto generate completions
helpPrint 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.

1

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.

KeyTypeOptionalDescription
actionstringnobinary.github
namestringnoname of binary locally after download
directorystringnodirectory to save the binary locally
repositorystringnoGithub repository
versionstringnoversion/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.

KeyTypeOptionalDescription
actionstringnocommand.run
commandstringnocommand to run
argsstringyesargument passed
dirstringyesactual working directory
privilegedboolyeselevate privileges when executing
envHashMap<string, string>yeskey 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.

KeyTypeOptionalDescription
actionstringnofile.copy
fromstringnosource file
tostringnodestination file
templatebooleanyesrenders files using context providers
default: false
chmodintegeryesoctal permissions
owned_by_userstringyesuser for chown
owned_by_groupstringyesgroup 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.

KeyTypeOptionalDescription
actionstringnofile.chown
pathstringnofile to change ownership on
userstringnouser to specify as file owner
groupstringnogroup 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.

KeyTypeOptionalDescription
actionstringnofile.download
fromstringnosource location
tostringnodestination file
owned_by_userstringyesuser for chown
owned_by_groupstringyesgroup 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.

Create a symlink for files. This action can be used to symlink a single file or files in a directory.

KeyTypeOptionalDescription
actionstringnofile.link
fromstringnosymlink location
tostringnosymlink points to
walk dirbooleanyesWalk diles in directory
default: false
sourcestringyesUsed 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.

KeyTypeOptionalDescription
actionstringnofile.remove
targetstringnofile 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.

KeyTypeOptionalDescription
actionstringnofile.unarchive
fromstringnofull path to archive
tostringnodestination of unarchived contents
forceboolyesforce 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.

KeyTypeOptionalDescription
actionstringnodirectory.copy
fromstringnosource directory
tostringnodestination 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.

KeyTypeOptionalDescription
actionstringnogit.clone
repo_urlstringnorepository to clone
directorystringnodirectory 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.

KeyTypeOptionalDescription
actionstringnogit.clone
repositorystringnorepository to clone
directorystringnodirectory 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

KeyTypeOptionalDescription
actionstringnogroup.add
group_namestringnoname of group to add

Example

actions:
  - action: group.add
    group_name: testgroup

macOS

  • macos.default

macos.default

KeyTypeOptionalDescription
actionstringnomacos.default
domainstringnoDomain: defaults or domains or https://macos-defaults.com
keystringnowhich key to change
kindstringnovalue type
valuestringnovalue

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.

ProviderOS
pacman/yayArch
paruArch
aptDebian/Ubuntu
pkgFreeBSD
pkginNetBSD (Multiple)
brewmacOS
wingetWindows
xbpsVoid Linux
zypperOpenSUSE
macportsmacOS
dnfFedora
snapcraftLinux

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

KeyTypeOptionalDescription
actionstringnopackage.install
namestringnoname of target package
listlistyeslist of multiple packages
providerstringyesSpecify package provider
repositorystringyesspecific repository for a provider and package
fileboolyesSpecify 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.

KeyTypeOoptionalDescription
actionstringnouser.add
fullnamestringnofull name of user
home_dirstringnohome directory of user
usernamestringnousername of user
shellstringyesshell for user
grouplistyesgroups 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.

KeyTypeOptionalDescription
actionstringnouser.group
usernamestringnousername of user to add to group
grouplistnogroups 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.

ProviderValue
Sudosudo
Doasdoas
Run0run0

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