A KISS (Keep It Simple, Stupid) configuration management system written in Ruby, designed for personal use.
This software has been written by a human by 90%, and only the last 10% were AI assisted. The main purpose of this project was to learn about Ruby metaprogramming.
- Quick Start
- Command-Line Options
- DSL Reference
- Resource Modifiers
- Dependencies
- Templates (ERB)
- Chained DSL Syntax
- Configuration File
- Backup System
- Development
See examples/rake/ for a working example.
# Rakefile
begin
require 'rcm'
rescue LoadError
require_relative '/path/to/rcm/lib/dsl'
end
task :setup do
configure do
given { hostname is :earth }
file '/tmp/wg0.conf' do
from template
'interface = <%= "wg0" %>'
end
end
endcd examples/rake
rake setup -- --dry
rake setup -- --debugSee examples/gem/ for a working example.
# Gemfile
gem 'rcm', path: '~/git/rcm'# config.rb
require 'rcm'
configure do
given { hostname is :earth }
file '/tmp/wg0.conf' do
from template
'interface = <%= "wg0" %>'
end
endbundle install
bundle exec ruby config.rb --drySee examples/plain_ruby/ for a working example.
#!/usr/bin/env ruby
begin
require 'rcm'
rescue LoadError
require_relative '/path/to/rcm/lib/dsl'
end
configure do
file '/tmp/hello.txt' do
'Hello World!'
end
endruby config.rb --drySee examples/cli/ for a working example.
rcm config.rb --dry --hosts earth,marsIn Rake mode, options go after --. In standalone mode, pass them directly.
| Option | Short | Description |
|---|---|---|
--dry |
-d |
Dry run mode, log actions without executing them |
--debug |
-v |
Enable debug output |
--hosts HOST1,HOST2 |
Only run on the listed hostnames (comma-separated) |
Examples:
rake setup -- --dry --debug
rake setup -- --hosts earth,mars
ruby config.rb --dry --hosts earth
rcm config.rb --dry --hosts earth,marsEntry points for configuration blocks.
# Standard entry point, accumulates resources across calls
configure do
# ...
end
# Resets all resource tracking before running (clean slate)
configure_from_scratch do
# ...
endconfigure_from_scratch resets the internal resource cache, useful in tests or when you need a clean state.
Create or manage files with content.
# Simple file with string content
file '/tmp/hello.txt' do
'Hello World!'
end
# File with array content (joined by newlines)
file '/tmp/list.txt' do
%w[Hello World and Hello Universe]
end
# File from an ERB template
file '/tmp/config.txt' do
from template
'Hostname: <%= Socket.gethostname %>'
end
# File copied from another file
file '/tmp/copy.txt' do
from sourcefile
'/etc/original.txt'
end
# File with parent directory creation
file '/tmp/deep/nested/dir/config.txt' do
manage directory
'content'
end
# Named file with explicit path
file create config do
path '/etc/myapp.conf'
manage directory
mode 0o644
'settings'
end
# Delete a file
file '/tmp/obsolete.txt' do
is absent
endCreate empty files, like the Unix touch command.
# Create an empty file
touch '/tmp/marker'
# Touch with permissions
touch '/tmp/secret' do
mode 0o600
end
# Touch with parent directory creation
touch '/var/log/myapp/status' do
manage directory
end
# Always update timestamp
touch '/tmp/heartbeat' do
is updated
endCreate and manage symbolic links.
# Create a symlink
symlink '/tmp/link' do
'/tmp/target'
end
# Symlink with dependency
symlink '/tmp/link' do
requires touch '/tmp/target'
'/tmp/target'
end
# Remove a symlink
symlink '/tmp/link' do
is absent
endCreate and manage directories.
# Create a directory
directory '/tmp/mydir' do
is present
end
# Create with permissions
directory '/tmp/secure' do
mode 0o700
owner 'root'
end
# Delete a directory
directory '/tmp/old' do
is absent
end
# Purge a directory (delete recursively including contents)
directory '/tmp/cache' do
is purged
without backup
end
# Recursively copy one directory into another
directory '/opt/backup' do
recursively
without backup
'/opt/original'
endConditionally execute all following resources based on system state.
configure do
given { hostname is :earth }
# Everything below only runs on host "earth"
file '/tmp/earth.txt' do
'This host is earth'
end
endWith --hosts, you can filter from the command line without changing the DSL:
rake setup -- --hosts earth,marsWhen --hosts is specified, the current hostname must be in the list for given blocks to pass, regardless of what the DSL condition says.
Print notification messages. Useful as dependency targets or for logging progress.
notify 'deployment complete' do
requires file '/etc/app.conf'
'Application deployed successfully'
endManage system packages (currently Fedora/DNF only, work in progress).
package 'nginx' do
is present
endThese modifiers are common across file-based resources (file, touch, symlink, directory).
is present # Resource should exist (default)
is absent # Resource should be deleted
is purged # Directory: delete recursively including contents
is updated # Touch only: always update the timestampmode 0o644 # Set file/directory permissions (octal)
owner 'username' # Set file owner
group 'groupname' # Set file groupmanage directory # Automatically create parent directories
recursively # For directories: recursive copy or deletefrom template # Process content as ERB template
from sourcefile # Content is a path to copy fromAppend or remove individual lines in a file.
# Append a line if not already present
file '/etc/hosts' do
line '192.168.1.100 myserver'
end
# Remove a line
file '/etc/hosts' do
line 'old.entry.to.remove'
is absent
endwithout backup # Don't create backups when modifying/deletingBy default, RCM backs up files before modification. See Backup System.
file create my config do
path '/etc/myapp.conf'
'content'
endThis lets you name a resource differently from its filesystem path, which is useful for dependency references.
Resources can declare dependencies on other resources. Dependencies are evaluated before the dependent resource.
configure do
file '/tmp/config.conf' do
'settings'
end
# This file is created after /tmp/config.conf
file '/tmp/app.conf' do
requires file '/tmp/config.conf'
'app settings'
end
endMultiple dependencies:
file '/tmp/final.txt' do
requires file '/tmp/a.txt' and requires file '/tmp/b.txt'
'done'
endNamed resources with dependencies:
configure do
touch create do
path '/tmp/marker'
end
touch update do
path '/tmp/marker'
is updated
requires touch create
end
endDependency loops are detected and reported.
Files can use ERB templates for dynamic content.
file '/tmp/config.txt' do
from template
'Server: <%= Socket.gethostname %>, Time: <%= Time.now %>'
endStandard ERB syntax applies: <%= expression %> for output, <% code %> for logic.
RCM uses Ruby metaprogramming to allow natural language-like syntax. Any undefined method call is silently absorbed and used as part of the resource identifier.
# All of these are valid:
given { hostname is :earth }
notify hello dear world do
thank you to be part of you
end
file create empty directory do
path '/tmp/file.txt'
manage directory
'content'
endThe chained words become the resource name/identifier (e.g., file('create empty directory')).
RCM can load configuration from a config.toml file in the project root.
[hostgroups]
frontends = ["web1.example.com", "web2.example.com"]Access in DSL:
configure do
hosts = config('hostgroups')['frontends']
endBy default, RCM creates backups before modifying or deleting files.
- Location:
.rcmbackup/directory next to the managed file - Naming:
filename.{sha256hash}for content changes,filename.{timestamp}for deletions - Deduplication: Identical content is not backed up twice (same hash = same backup)
Disable per-resource with without backup:
file '/tmp/disposable.txt' do
without backup
'content'
end# Install dependencies
bundle install
# Run all tests
rake test
# Run a specific test file
rake test TEST=test/lib/dslkeywords/file_test.rb
# Run a playground task
cd playground
rake wireguard -- --dry
rake wireguard -- --debugFor more examples, check out the tests and the playground.
