Skip to content

snonux/rcm

Repository files navigation

RCM - Ruby Configuration Management

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.

RCM DSL syntax

Table of Contents

Quick Start

With Rake (from the playground)

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
end
cd examples/rake
rake setup -- --dry
rake setup -- --debug

As a Gem (from any directory)

See 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
end
bundle install
bundle exec ruby config.rb --dry

Plain Ruby Script

See 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
end
ruby config.rb --dry

Via CLI

See examples/cli/ for a working example.

rcm config.rb --dry --hosts earth,mars

Command-Line Options

In 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,mars

DSL Reference

configure / configure_from_scratch

Entry 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
  # ...
end

configure_from_scratch resets the internal resource cache, useful in tests or when you need a clean state.

file

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
end

touch

Create 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
end

symlink

Create 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
end

directory

Create 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'
end

given

Conditionally 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
end

With --hosts, you can filter from the command line without changing the DSL:

rake setup -- --hosts earth,mars

When --hosts is specified, the current hostname must be in the list for given blocks to pass, regardless of what the DSL condition says.

notify

Print notification messages. Useful as dependency targets or for logging progress.

notify 'deployment complete' do
  requires file '/etc/app.conf'
  'Application deployed successfully'
end

package

Manage system packages (currently Fedora/DNF only, work in progress).

package 'nginx' do
  is present
end

Resource Modifiers

These modifiers are common across file-based resources (file, touch, symlink, directory).

State Management

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 timestamp

Permissions

mode 0o644          # Set file/directory permissions (octal)
owner 'username'    # Set file owner
group 'groupname'   # Set file group

Directory Management

manage directory    # Automatically create parent directories
recursively         # For directories: recursive copy or delete

Content Source

from template       # Process content as ERB template
from sourcefile     # Content is a path to copy from

Line Management

Append 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
end

Backup Control

without backup    # Don't create backups when modifying/deleting

By default, RCM backs up files before modification. See Backup System.

Path Override

file create my config do
  path '/etc/myapp.conf'
  'content'
end

This lets you name a resource differently from its filesystem path, which is useful for dependency references.

Dependencies

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
end

Multiple dependencies:

file '/tmp/final.txt' do
  requires file '/tmp/a.txt' and requires file '/tmp/b.txt'
  'done'
end

Named resources with dependencies:

configure do
  touch create do
    path '/tmp/marker'
  end

  touch update do
    path '/tmp/marker'
    is updated
    requires touch create
  end
end

Dependency loops are detected and reported.

Templates (ERB)

Files can use ERB templates for dynamic content.

file '/tmp/config.txt' do
  from template
  'Server: <%= Socket.gethostname %>, Time: <%= Time.now %>'
end

Standard ERB syntax applies: <%= expression %> for output, <% code %> for logic.

Chained DSL Syntax

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'
end

The chained words become the resource name/identifier (e.g., file('create empty directory')).

Configuration File

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']
end

Backup System

By 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

Development

# 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 -- --debug

For more examples, check out the tests and the playground.

About

Ruby configuration management system (rcm) - KISS and for my personal use.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages