cramae
cramae
A configuration management tool inspired by mitamae, rewritten in Crystal. Fast, single-binary provisioning with a mitamae-compatible DSL.
Why
mitamae uses mruby for a Chef-like DSL — fast and single-binary. cramae brings the same experience to Crystal:
- Crystal-native DSL — mitamae-like syntax using Crystal blocks and
withreceiver - Single binary — compile and deploy, no runtime dependencies
- Type-safe — Crystal's compiler catches errors before execution
- Dual format — Crystal DSL or YAML recipes
Quick Start
Install
# Build from source (requires Crystal >= 1.10)
git clone https://github.com/example/cramae.git
cd cramae
shards install
crystal build src/cli_entry.cr -o /usr/local/bin/cramae
Your first recipe (Crystal DSL)
# recipe.cr
require "cramae"
Cramae.recipe do
package "nginx" do
action :install
end
service "nginx" do
action [:enable, :start]
end
file "/etc/nginx/nginx.conf" do
content <<-CONF
worker_processes 1;
events { worker_connections 1024; }
http {
server {
listen 80;
location / { root /var/www/html; }
}
}
CONF
mode "644"
owner "root"
group "root"
notifies :reload, "service[nginx]"
end
directory "/var/www/html" do
mode "755"
owner "www-data"
group "www-data"
end
file "/var/www/html/index.html" do
content "<h1>Hello from Cramae!</h1>\n"
mode "644"
end
end
cramae run recipe.cr # apply
cramae run --dry-run recipe.cr # preview
Or use YAML recipes
# recipe.yml
resources:
- type: package
name: nginx
action: install
- type: service
name: nginx
action: enable,start
- type: file
path: /etc/nginx/nginx.conf
content: |
server { listen 80; }
mode: "644"
owner: root
group: root
cramae local recipe.yml
cramae local --dry-run recipe.yml # preview
Commands
| Command | Description |
|---|---|
cramae local RECIPE... |
Run YAML recipes |
cramae run RECIPE... |
Run Crystal DSL recipes |
cramae version |
Print version |
cramae help [COMMAND] |
Show help |
Options
| Flag | Description |
|---|---|
-n, --dry-run |
Preview changes without applying |
-l, --log-level LEVEL |
Set log level (debug, info, warn, error) |
-j, --node-json FILE |
Load node attributes from JSON (local only) |
-y, --node-yaml FILE |
Load node attributes from YAML (local only) |
--color, --no-color |
Enable/disable colored output |
Resource Reference
file
Manage file content, permissions, and ownership.
file "/etc/hosts" do
action :create # create, delete, edit
content "127.0.0.1 localhost\n"
mode "644"
owner "root"
group "root"
sensitive false # hide content in diffs
atomic_update false # atomic via tempfile + rename
not_if "test -f /etc/hosts"
end
directory
directory "/var/www" do
action :create # create, delete
mode "755"
owner "www-data"
group "www-data"
end
execute
execute "systemctl daemon-reload" do
action :run
cwd "/tmp"
only_if "test -f /etc/systemd/system/nginx.service"
end
package
package "nginx" do
action :install # install, remove
version "1.24.0"
end
service
service "nginx" do
action [:enable, :start] # start, stop, restart, reload, enable, disable
end
template
Supports {{ variable }} substitution.
template "/etc/nginx/sites-enabled/default" do
source "default.erb" # auto-looks in templates/
mode "644"
variables({"port" => "80", "server_name" => "example.com"})
end
git
git "/opt/myapp" do
repository "https://github.com/example/myapp.git"
revision "main"
recursive true
depth 1
end
link
link "/usr/local/bin/myapp" do
to "/opt/myapp/bin/myapp"
force true
end
user / group
user "myapp" do
uid 1001
gid "myapp"
shell "/bin/bash"
home "/home/myapp"
create_home true
end
group "myapp" do
gid 1001
end
gem_package
gem_package "bundler" do
action :install # install, uninstall, upgrade
version "2.4.0"
end
http_request
http_request "/tmp/data.json" do
action :get # get, post, put, delete
url "https://api.example.com/data"
headers({"Authorization" => "Bearer token"})
end
remote_file / remote_directory
remote_file "/etc/config.yml" do
source "files/config.yml" # auto-looks in files/
mode "600"
end
remote_directory "/opt/static" do
source "files/static/"
mode "755"
end
Notifications & Subscriptions
Trigger actions on other resources when a resource changes:
file "/etc/nginx/nginx.conf" do
content "..."
notifies :reload, "service[nginx]" # delayed by default
notifies :restart, "service[nginx]", :immediately
end
service "nginx" do
action :nothing # only act when notified
subscribes :reload, "file[/etc/nginx/nginx.conf]"
end
Conditional Execution
file "/tmp/cache" do
content "data"
only_if "test -f /etc/app.conf" # run only if command succeeds
not_if "test -f /tmp/cache" # skip if command succeeds
end
Node Attributes
cramae local --node-json nodes/production.json recipe.yml
{"env": "production", "hostname": "web01"}
Project Structure
src/
├── cli_entry.cr # Binary entry point
├── cramae.cr # Library entry, global Log
└── cramae/
├── backend.cr # Shell command execution
├── cli.cr # CLI parsing
├── command_result.cr # Command execution result
├── dsl.cr # Crystal-native DSL
├── logger.cr # Colored, indented logging
├── node.cr # Node attributes
├── recipe.cr # Recipe, RecipeLoader
├── recipe_executor.cr# Recipe execution engine
├── resource.cr # 15 resource types
├── shellwords.cr # Shell escaping
├── specinfra.cr # Native system operations
└── executors/ # One executor per resource type
Development
shards install
crystal build src/cli_entry.cr -o bin/cramae
crystal spec # 46 tests
crystal spec spec/cramae/dsl_spec.cr # specific test file
See CONTRIBUTING.md for setup, code style, and how to add a new resource type, and CHANGELOG.md for release history. AI coding agents should read AGENTS.md before making changes.
License
MIT
Repository
cramae
Owner
Statistic
- 0
- 0
- 0
- 0
- 1
- about 4 hours ago
- June 24, 2026
License
MIT License
Links
Synced at
Wed, 24 Jun 2026 09:41:47 GMT
Languages