crops 2.4.0
Crystal Ops (crops)
This is a port of
opsto Crystal. Versions start at 2.0.0, and the executable is stillops.opsremains available viagemasops_team. See bottom of file for differences betweencropsandops.
The code in this repo is currently hot garbage, due mostly to having been ported from Ruby without significant refactoring (it's certainly not due to my being new to Crystal). The refactor is a work in progress. However, the tool works.
Jump to the installation instructions.
Overview
ops lets you add shell commands to ops.yml and run them via shortcuts when you're in that directory. ops.yml becomes a context-aware place to add common commands.

The commands you run to work with your project become discoverable to other devs. You can have different commands in different directories, because ops always looks for ops.yml in your current working directory.

New devs don't need to find which Rakefile contains the task that failed. Just run ops help.

You no longer have to write a shell script for that long curl command that hits your API in dev, just run ops create-event.

ops will encrypt your SSH keys using a passphrase from an EJSON file, and never prompt you for the passphrase:

This passphrase and other secrets for your project can be kept in an environment-specific EJSON file, which ops will automatically load for you every time it runs, if you have ejson installed. This lets you commit the secrets safely, while only sharing EJSON keys for each environment with other developers or a CI/CD pipeline.
Dependencies
You can record dependencies for your project in ops.yml:

and ops up will satisfy them for you.

The following dependency types are supported:
brew: installs a package using Homebrew if running on a Maccask: installs a Homebrew cask if running on a Macapt: installs a package usingaptif running on debian-based linuxapk: installs a package usingapkif running on alpine linuxgem: installs a gemdocker: usesdocker-composeto start and stop a service in a subdirectory of your projectcustom: runs a custom shell commanddir: creates a local directory (for when your app needs a directory, but there are no checked-in files in it)sshkey: creates an SSH key at the given path, if it doesn't already exist; can be configured to encrypt the private key with a passphrase
Config and Secrets
ops will try to load config/$environment/config.yml (or .yaml, or .json) and config/$environment/secrets.ejson when you run it. If these files aren't present, no problem.
$environmentis a variableopsuses to detect which environment it's running in.opsassumes the environment isdevby default, and you canexport environment=staging, for example, to change the current environment.
If these files are present, ops will load every key:value pair it finds under .environment into environment variables.

For an EJSON file, ops will first decrypt these values, then load them.
You must have the executable
ejsonin your path to use this feature. You can install it viagemor by using the Shopify Homebrew tapshopify/shopify.

This allows you to check in most of your secrets safely, and transparently load them when running your code.
Loading Order
When ops runs an action, environment variables are loaded from multiple sources in the following order:
- Config (
config/$environment/config.yml,.yaml, or.json) - loaded first - Secrets (
config/$environment/secrets.ejsonor.json) - loaded second (only if the action hasload_secrets: true) - Options.environment (from
ops.yml) - loaded last
Since each source sets environment variables directly, later sources override earlier ones. This means variables defined in options.environment in your ops.yml will have the highest priority.
Example ops.yml using options.environment:
options:
environment:
DATABASE_URL: postgres://localhost/myapp
API_KEY: $SECRET_API_KEY # Can reference variables from config/secrets
This allows you to:
- Define base configuration in
config.json - Store secrets in
secrets.ejson - Override or add additional environment variables in
ops.ymlthat are shared across all environments
Installation
Via gem
For the Crystallized version of ops from this repo:
brew install bdw-gc libevent libyaml pcre2
gem install ops_team -v 2.0.0.rc20
For the plain ol' Ruby version:
gem install ops_team
Via brew
brew tap nickthecook/crops
brew install ops
Via GitHub Release
There are tarballs of binaries in the Releases for this project. Just extract one, copy the binary for your platform into your $PATH, and run ops version to make sure it worked.
Differences between crops and ops
crops does not support:
- the
backgroundbuiltin (bg) - the
background-logbuiltin (bglog) - performance profiling
- the
sshkey.passphraseoption (usessh.passphrase_varinstead; default isSSH_KEY_PASSPHRASE)
The following things are different between crops and ops:
- default template dir for
ops initis$HOME/.ops_templates; override with optioninit.template_dirorOPS__INIT__TEMPLATE_DIR
Things that are different from ops but will be fixed
- "did you mean...?" suggestions
Builtins
ops provides several built-in commands that you can use without defining them in ops.yml:
countdown <seconds>: Likesleep, but displays time remaining in terminaldown [dependency...]: Unmeets dependencies listed inops.yml(opposite ofup). Optionally specify which dependencies to unmeetenv: Prints the current environment (e.g. 'dev', 'production', 'staging')envdiff <env1> <env2>: Compares keys present in config and secrets between different environmentsexec <command>: Executes the given command in theopsenvironment, with environment variables sethelp(alias:h): Displays available builtins, actions, and forwardsinit [template]: Creates anops.ymlfile from a templateup [dependency...]: Attempts to meet dependencies listed inops.yml. Optionally specify which dependencies to meetversion(alias:v): Prints the version ofopsthat is running
Options
ops supports options to change various behaviours:
snap.install- if
true,opswill install snaps listed underdependencies - default:
true
- if
snap.use_sudo- if
true,opswill usesudoto runsnapcommands - default:
true
- if
gem.use_sudo- if
true,opswill usesudoto rungem installcommands - default: false
- if
gem.user_install- if
true,opswill pass the--user-installoption togem installcommands - default: false
- if
pip.commandopswill use the value as the command to invokepip- default:
python3 -m pip
sshkey.key_sizeopswill create private SSH keys with this size- default:
4096
sshkey.key_algoopswill use this value as the SSH key algorithm- default:
rsa
sshkey.passphrase_varopswill use this value as the name of the environment variable to read the SSH key passphrase from- default:
SSH_KEY_PASSPHRASE
sshkey.add_keys- if
false,opswill not attempt to add SSH keys it loads to the SSH agent - default:
true
- if
sshkey.key_lifetimeopswill set the key lifetime of SSH keys it adds to the agent to this number of seconds- default:
3600(1 hour)
sshkey.key_file_commentopswill use this value as the key comment when adding SSH keys to the SSH agent- this comment is visible in
ssh-add -l, allowing you to identify which keys are loaded - default:
<user>@<hostname -s>
apt.use_sudo- if
true,opswill usesudoto runaptcommands when not root - default:
true
- if
exec.load_secrets- if
true,opswill load secrets before running anops execcommand - default:
false
- if
init.template_dirops initwill look in this directory forops.ymltemplates- default:
$HOME/.ops_templates
envdiff.ignored_keysops envdiffwill omit these keys when showing the diff between two environments' configs- default: empty
up.fail_on_error- if
true,ops upwill exit with an error if it fails to meet any dependency - default:
false
- if
up.exit_on_error- if
true,ops upwill exit immediately if it fails to meet any dependency, instead of trying to satisfy the remaining dependencies - default:
false
- if
config.pathopswill look for a config file at this path (supports JSON or YAML)- default:
config/$environment/config.yml,config/$environment/config.yaml, orconfig/$environment/config.json(first found)
secrets.pathopswill look at for an EJSON secrets file at this path- default:
config/$environment/secrets.json
environment_aliasesopswill duplicate the$environmentvariable to other variables- intended for use with languages/frameworks that use a different env var to set the environment
Options can be specified in ops.yml under the top-level options: second, or as environment variables. E.g., setting exec.load_secrets via ops.yml:
options:
exec:
load_secrets: true
And via ENV var:
OPS__EXEC__LOAD_SECRETS=true ops exec ...
crops
- 24
- 3
- 12
- 0
- 1
- about 4 hours ago
- March 22, 2023
GNU General Public License v3.0
Fri, 21 Nov 2025 17:58:52 GMT