1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>RCM: The Ruby Configuration Management DSL</title>
<link rel="shortcut icon" type="image/gif" href="/favicon.ico" />
<link rel="stylesheet" href="../style.css" />
<link rel="stylesheet" href="style-override.css" />
</head>
<body>
<div class="rfx-overlay-grid"></div>
<div class="rfx-overlay-scanlines"></div>
<div id="rfx-stars"></div>
<div class="rfx-vignette"></div>
<p class="header">
<a href="https://foo.zone">Home</a> | <a href="https://codeberg.org/snonux/foo.zone/src/branch/content-md/gemfeed/2026-03-02-rcm-ruby-configuration-management-dsl.md">Markdown</a> | <a href="gemini://foo.zone/gemfeed/2026-03-02-rcm-ruby-configuration-management-dsl.gmi">Gemini</a>
</p>
<h1 style='display: inline' id='rcm-the-ruby-configuration-management-dsl'>RCM: The Ruby Configuration Management DSL</h1><br />
<br />
<span class='quote'>Published at 2026-03-02T00:00:00+02:00</span><br />
<br />
<span>RCM is a tiny configuration management system written in Ruby. It gives me a small DSL for describing how I want my machines to look, then it applies the changes: create files and directories, manage packages, and make sure certain lines exist in configuration files. It's deliberately KISS and optimised for a single person's machines instead of a whole fleet.</span><br />
<br />
<a href='./rcm-ruby-configuration-management-dsl/rcm-dsl.png'><img alt='RCM DSL in action' title='RCM DSL in action' src='./rcm-ruby-configuration-management-dsl/rcm-dsl.png' /></a><br />
<br />
<h2 style='display: inline' id='table-of-contents'>Table of Contents</h2><br />
<br />
<ul>
<li><a href='#rcm-the-ruby-configuration-management-dsl'>RCM: The Ruby Configuration Management DSL</a></li>
<li>⇢ <a href='#why-i-built-rcm'>Why I built RCM</a></li>
<li>⇢ <a href='#how-the-dsl-feels'>How the DSL feels</a></li>
<li>⇢ ⇢ <a href='#keywords-and-resources'>Keywords and resources</a></li>
<li>⇢ ⇢ <a href='#files-directories-and-templates'>Files, directories, and templates</a></li>
<li>⇢ <a href='#how-ruby-s-metaprogramming-helps'>How Ruby's metaprogramming helps</a></li>
<li>⇢ ⇢ <a href='#a-bit-more-about-methodmissing'>A bit more about <span class='inlinecode'>method_missing</span></a></li>
<li>⇢ <a href='#ruby-metaprogramming-further-reading'>Ruby metaprogramming: further reading</a></li>
<li>⇢ <a href='#safety-dry-runs-and-debugging'>Safety, dry runs, and debugging</a></li>
<li>⇢ <a href='#rcm-vs-puppet-and-other-big-tools'>RCM vs Puppet and other big tools</a></li>
<li>⇢ <a href='#cutting-rcm-010'>Cutting RCM 0.1.0</a></li>
<li>⇢ <a href='#what-s-next'>What's next</a></li>
<li>⇢ <a href='#feature-overview-for-now'>Feature overview (for now)</a></li>
<li>⇢ ⇢ <a href='#template-rendering-into-a-file'>Template rendering into a file</a></li>
<li>⇢ ⇢ <a href='#ensuring-a-line-is-absent-from-a-file'>Ensuring a line is absent from a file</a></li>
<li>⇢ ⇢ <a href='#guarding-a-configuration-run-on-the-current-hostname'>Guarding a configuration run on the current hostname</a></li>
<li>⇢ ⇢ <a href='#creating-and-deleting-directories-and-purging-a-directory-tree'>Creating and deleting directories, and purging a directory tree</a></li>
<li>⇢ ⇢ <a href='#managing-file-and-directory-modes-and-ownership'>Managing file and directory modes and ownership</a></li>
<li>⇢ ⇢ <a href='#using-a-chained-more-natural-language-style-for-notifications'>Using a chained, more natural language style for notifications</a></li>
<li>⇢ ⇢ <a href='#touching-files-and-updating-their-timestamps'>Touching files and updating their timestamps</a></li>
<li>⇢ ⇢ <a href='#expressing-dependencies-between-notifications'>Expressing dependencies between notifications</a></li>
<li>⇢ ⇢ <a href='#creating-and-updating-symbolic-links'>Creating and updating symbolic links</a></li>
<li>⇢ ⇢ <a href='#detecting-duplicate-resource-definitions-at-configure-time'>Detecting duplicate resource definitions at configure time</a></li>
</ul><br />
<h2 style='display: inline' id='why-i-built-rcm'>Why I built RCM</h2><br />
<br />
<span>I've used (and still use) the usual suspects in configuration management: Puppet, Ansible, etc. They are powerful, but also come with orchestration layers, agents, inventories, and a lot of moving parts. For my personal machines I wanted something smaller: one Ruby process, one configuration file, a few resource types, and good enough safety features.</span><br />
<br />
<span>I've always been a fan of Ruby's metaprogramming features, and this project let me explore them in a focused, practical way.</span><br />
<br />
<span>Because of that metaprogramming support, Ruby is a great fit for DSLs. You can get very close to natural language without inventing a brand-new syntax. RCM leans into that: the goal is to read a configuration and understand what happens without jumping between multiple files or templating languages.</span><br />
<br />
<a class='textlink' href='https://codeberg.org/snonux/rcm'>RCM repo on Codeberg</a><br />
<br />
<h2 style='display: inline' id='how-the-dsl-feels'>How the DSL feels</h2><br />
<br />
<span>An RCM configuration starts with a <span class='inlinecode'>configure</span> block. Inside it you declare resources (<span class='inlinecode'>file</span>, <span class='inlinecode'>package</span>, <span class='inlinecode'>given</span>, <span class='inlinecode'>notify</span>, …). RCM figures out dependencies between resources and runs them in the right order.</span><br />
<br />
<!-- Generator: GNU source-highlight 3.1.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><font color="#ff0000">configure </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> given </font><font color="#F3E651">{</font><font color="#ff0000"> hostname is </font><font color="#F3E651">:</font><font color="#ff0000">earth </font><font color="#F3E651">}</font>
<font color="#ff0000"> file </font><font color="#bb00ff">'/tmp/test/wg0.conf'</font><font color="#ff0000"> </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> requires file </font><font color="#bb00ff">'/etc/hosts.test'</font>
<font color="#ff0000"> manage directory</font>
<font color="#ff0000"> from template</font>
<font color="#ff0000"> </font><font color="#bb00ff">'content with <%= 1 + 2 %>'</font>
<font color="#ff0000"> </font><b><font color="#ffffff">end</font></b>
<font color="#ff0000"> file </font><font color="#bb00ff">'/etc/hosts.test'</font><font color="#ff0000"> </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> line </font><font color="#bb00ff">'192.168.1.101 earth'</font>
<font color="#ff0000"> </font><b><font color="#ffffff">end</font></b>
<b><font color="#ffffff">end</font></b>
</pre>
<br />
<span>Which would look like this when run:</span><br />
<br />
<!-- Generator: GNU source-highlight 3.1.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><font color="#F3E651">%</font><font color="#ff0000"> sudo ruby example</font><font color="#F3E651">.</font><font color="#ff0000">rb</font>
<font color="#ff0000">INFO </font><font color="#bb00ff">20260301</font><font color="#ff0000">-</font><font color="#bb00ff">213817</font><font color="#ff0000"> dsl</font><font color="#F3E651">(</font><font color="#bb00ff">0</font><font color="#F3E651">)</font><font color="#ff0000"> </font><font color="#F3E651">=></font><font color="#ff0000"> Configuring</font><font color="#F3E651">...</font>
<font color="#ff0000">INFO </font><font color="#bb00ff">20260301</font><font color="#ff0000">-</font><font color="#bb00ff">213817</font><font color="#ff0000"> file</font><font color="#F3E651">(</font><font color="#bb00ff">'/tmp/test/wg0.conf'</font><font color="#F3E651">)</font><font color="#ff0000"> </font><font color="#F3E651">=></font><font color="#ff0000"> Registered dependency on file</font><font color="#F3E651">(</font><font color="#bb00ff">'/etc/hosts.test'</font><font color="#F3E651">)</font>
<font color="#ff0000">INFO </font><font color="#bb00ff">20260301</font><font color="#ff0000">-</font><font color="#bb00ff">213817</font><font color="#ff0000"> file</font><font color="#F3E651">(</font><font color="#bb00ff">'/tmp/test/wg0.conf'</font><font color="#F3E651">)</font><font color="#ff0000"> </font><font color="#F3E651">=></font><font color="#ff0000"> Evaluating</font><font color="#F3E651">...</font>
<font color="#ff0000">INFO </font><font color="#bb00ff">20260301</font><font color="#ff0000">-</font><font color="#bb00ff">213817</font><font color="#ff0000"> file</font><font color="#F3E651">(</font><font color="#bb00ff">'/etc/hosts.test'</font><font color="#F3E651">)</font><font color="#ff0000"> </font><font color="#F3E651">=></font><font color="#ff0000"> Evaluating</font><font color="#F3E651">...</font>
<font color="#ff0000">INFO </font><font color="#bb00ff">20260301</font><font color="#ff0000">-</font><font color="#bb00ff">213817</font><font color="#ff0000"> file</font><font color="#F3E651">(</font><font color="#bb00ff">'/etc/hosts.test'</font><font color="#F3E651">)</font><font color="#ff0000"> </font><font color="#F3E651">=></font><font color="#ff0000"> Writing file /etc/hosts</font><font color="#F3E651">.</font><b><font color="#ffffff">test</font></b>
<font color="#ff0000">INFO </font><font color="#bb00ff">20260301</font><font color="#ff0000">-</font><font color="#bb00ff">213817</font><font color="#ff0000"> file</font><font color="#F3E651">(</font><font color="#bb00ff">'/tmp/test/wg0.conf'</font><font color="#F3E651">)</font><font color="#ff0000"> </font><font color="#F3E651">=></font><font color="#ff0000"> Creating parent directory /tmp/test</font>
<font color="#ff0000">INFO </font><font color="#bb00ff">20260301</font><font color="#ff0000">-</font><font color="#bb00ff">213817</font><font color="#ff0000"> file</font><font color="#F3E651">(</font><font color="#bb00ff">'/tmp/test/wg0.conf'</font><font color="#F3E651">)</font><font color="#ff0000"> </font><font color="#F3E651">=></font><font color="#ff0000"> Writing file /tmp/test/wg</font><font color="#bb00ff">0</font><font color="#F3E651">.</font><font color="#ff0000">conf</font>
</pre>
<br />
<span>The idea is that you describe the desired state and RCM worries about the steps. The <span class='inlinecode'>given</span> block can short‑circuit the whole run (for example, only run on a specific hostname). Each <span class='inlinecode'>file</span> resource can either manage a complete file (from a template) or just make sure individual lines are present.</span><br />
<br />
<h3 style='display: inline' id='keywords-and-resources'>Keywords and resources</h3><br />
<br />
<span>Under the hood, each DSL word is either a keyword or a resource:</span><br />
<br />
<ul>
<li><span class='inlinecode'>Keyword</span> is the base class for all top‑level DSL constructs.</li>
<li><span class='inlinecode'>Resource</span> is the base class for things RCM can manage (files, packages, and so on).</li>
</ul><br />
<span>Resources can declare dependencies with <span class='inlinecode'>requires</span>. Before a resource runs, RCM makes sure all its requirements are satisfied and only evaluates each resource once per run. This keeps the mental model simple even when you compose more complex configurations.</span><br />
<br />
<h3 style='display: inline' id='files-directories-and-templates'>Files, directories, and templates</h3><br />
<br />
<span>The <span class='inlinecode'>file</span> resource handles three common cases:</span><br />
<br />
<ul>
<li>Managing parent directories (<span class='inlinecode'>manage directory</span>) so you don't have to create them manually.</li>
<li>Rendering ERB templates (<span class='inlinecode'>from template</span>) so you can mix Ruby expressions into config files.</li>
<li>Ensuring individual lines exist (<span class='inlinecode'>line</span>) for the many "append this line if missing" situations.</li>
</ul><br />
<span>Every write operation creates a backup copy in <span class='inlinecode'>.rcmbackup/</span>, so you can always inspect what changed and roll back manually if needed.</span><br />
<br />
<h2 style='display: inline' id='how-ruby-s-metaprogramming-helps'>How Ruby's metaprogramming helps</h2><br />
<br />
<span>The nice thing about RCM is that the Ruby code you write in your configuration is not that different from the Ruby code inside RCM itself. The DSL is just a thin layer on top.</span><br />
<br />
<span>For example, when you write:</span><br />
<br />
<!-- Generator: GNU source-highlight 3.1.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><font color="#ff0000">file </font><font color="#bb00ff">'/etc/hosts.test'</font><font color="#ff0000"> </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> line </font><font color="#bb00ff">'192.168.1.101 earth'</font>
<b><font color="#ffffff">end</font></b>
</pre>
<br />
<span>Ruby turns <span class='inlinecode'>file</span> into a method call and <span class='inlinecode'>'/etc/hosts.test'</span> into a normal argument. Inside RCM, that method builds a <span class='inlinecode'>File</span> resource object and stores it for later. The block you pass is just a Ruby block; RCM calls it with the file resource as <span class='inlinecode'>self</span>, so method calls like <span class='inlinecode'>line</span> configure that resource. There is no special parser here, just plain Ruby method and block dispatch.</span><br />
<br />
<span>The same goes for constructs like:</span><br />
<br />
<!-- Generator: GNU source-highlight 3.1.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><font color="#ff0000">given </font><font color="#F3E651">{</font><font color="#ff0000"> hostname is </font><font color="#F3E651">:</font><font color="#ff0000">earth </font><font color="#F3E651">}</font>
</pre>
<br />
<span>RCM uses Ruby's dynamic method lookup to interpret <span class='inlinecode'>hostname</span> and <span class='inlinecode'>is</span> in that block and to decide whether the rest of the configuration should run at all. Features like <span class='inlinecode'>method_missing</span>, blocks, and the ability to change what <span class='inlinecode'>self</span> means in a block make this kind of DSL possible with very little code. You still get all the power of Ruby (conditionals, loops, helper methods), but the surface reads like a small language of its own.</span><br />
<br />
<h3 style='display: inline' id='a-bit-more-about-methodmissing'>A bit more about <span class='inlinecode'>method_missing</span></h3><br />
<br />
<span><span class='inlinecode'>method_missing</span> is one of the key tools that make the RCM DSL feel natural. In plain Ruby, if you call a method that does not exist, you get a <span class='inlinecode'>NoMethodError</span>. But before Ruby raises that error, it checks whether the object implements <span class='inlinecode'>method_missing</span>. If it does, Ruby calls that instead and lets the object decide what to do.</span><br />
<br />
<span>In RCM, you can write things like:</span><br />
<br />
<!-- Generator: GNU source-highlight 3.1.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><font color="#ff0000">given </font><font color="#F3E651">{</font><font color="#ff0000"> hostname is </font><font color="#F3E651">:</font><font color="#ff0000">earth </font><font color="#F3E651">}</font>
</pre>
<br />
<span>Inside that block, calls such as <span class='inlinecode'>hostname</span> and <span class='inlinecode'>is</span> don't map to normal Ruby methods. Instead, RCM's DSL objects see those calls in <span class='inlinecode'>method_missing</span>, and interpret them as "check the current hostname" and "compare it to this symbol". This lets the DSL stay small and flexible: adding a new keyword can be as simple as handling another case in <span class='inlinecode'>method_missing</span>, without changing the Ruby syntax at all.</span><br />
<br />
<span>Put differently: you can write what looks like a tiny English sentence (<span class='inlinecode'>hostname is :earth</span>) and Ruby breaks it into method calls (<span class='inlinecode'>hostname</span>, then <span class='inlinecode'>is</span>) that RCM can interpret dynamically. Those "barewords" are not special syntax; they are just regular Ruby method names that the DSL catches and turns into configuration logic at runtime.</span><br />
<br />
<span>Here's a simplified sketch of how such a condition object could look in Ruby:</span><br />
<br />
<!-- Generator: GNU source-highlight 3.1.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><b><font color="#ffffff">class</font></b><font color="#ff0000"> HostCondition</font>
<font color="#ff0000"> </font><b><font color="#ffffff">def</font></b><font color="#ff0000"> initialize</font>
<font color="#ff0000"> </font><b><font color="#F35E1E">@current_hostname</font></b><font color="#ff0000"> </font><font color="#F3E651">=</font><font color="#ff0000"> Socket</font><font color="#F3E651">.</font><font color="#ff0000">gethostname</font><font color="#F3E651">.</font><font color="#ff0000">to_sym</font>
<font color="#ff0000"> </font><b><font color="#ffffff">end</font></b>
<font color="#ff0000"> </font><b><font color="#ffffff">def</font></b><font color="#ff0000"> method_missing</font><font color="#F3E651">(</font><font color="#ff0000">name</font><font color="#F3E651">,</font><font color="#ff0000"> </font><font color="#F3E651">*</font><font color="#ff0000">args</font><font color="#F3E651">,</font><font color="#ff0000"> </font><font color="#F3E651">&)</font>
<font color="#ff0000"> </font><b><font color="#ffffff">case</font></b><font color="#ff0000"> name</font>
<font color="#ff0000"> </font><b><font color="#ffffff">when</font></b><font color="#ff0000"> </font><font color="#F3E651">:</font><font color="#ff0000">hostname</font>
<font color="#ff0000"> </font><b><font color="#F35E1E">@left</font></b><font color="#ff0000"> </font><font color="#F3E651">=</font><font color="#ff0000"> </font><b><font color="#F35E1E">@current_hostname</font></b>
<font color="#ff0000"> </font><b><font color="#ffffff">self</font></b><font color="#ff0000"> </font><i><font color="#ababab"># allow chaining: hostname is :earth</font></i>
<font color="#ff0000"> </font><b><font color="#ffffff">when</font></b><font color="#ff0000"> </font><font color="#F3E651">:</font><font color="#ff0000">is</font>
<font color="#ff0000"> </font><b><font color="#F35E1E">@left</font></b><font color="#ff0000"> </font><font color="#F3E651">==</font><font color="#ff0000"> args</font><font color="#F3E651">.</font><font color="#ff0000">first</font>
<font color="#ff0000"> </font><b><font color="#ffffff">else</font></b>
<font color="#ff0000"> </font><b><font color="#ffffff">super</font></b>
<font color="#ff0000"> </font><b><font color="#ffffff">end</font></b>
<font color="#ff0000"> </font><b><font color="#ffffff">end</font></b>
<b><font color="#ffffff">end</font></b>
<font color="#ff0000">HostCondition</font><font color="#F3E651">.</font><font color="#ff0000">new</font><font color="#F3E651">.</font><font color="#ff0000">hostname</font><font color="#F3E651">.</font><font color="#ff0000">is</font><font color="#F3E651">(:</font><font color="#ff0000">earth</font><font color="#F3E651">)</font>
</pre>
<br />
<span>RCM's real code is more sophisticated, but the idea is the same: Ruby happily calls <span class='inlinecode'>method_missing</span> for unknown methods like <span class='inlinecode'>hostname</span> and <span class='inlinecode'>is</span>, and the DSL turns those calls into a value (<span class='inlinecode'>true</span>/<span class='inlinecode'>false</span>) that decides whether the rest of the configuration should run.</span><br />
<br />
<h2 style='display: inline' id='ruby-metaprogramming-further-reading'>Ruby metaprogramming: further reading</h2><br />
<br />
<span>If you want to dive deeper into the ideas behind RCM's DSL, these books are great starting points:</span><br />
<br />
<ul>
<li>"Metaprogramming Ruby 2" by Paolo Perrotta</li>
<li>"The Well-Grounded Rubyist" by David A. Black (and others)</li>
<li>"Eloquent Ruby" by Russ Olsen</li>
</ul><br />
<span>They all cover Ruby's object model, blocks, <span class='inlinecode'>method_missing</span>, and other metaprogramming techniques in much more detail than I can in a single blog post.</span><br />
<br />
<h2 style='display: inline' id='safety-dry-runs-and-debugging'>Safety, dry runs, and debugging</h2><br />
<br />
<span>RCM has a <span class='inlinecode'>--dry</span> mode: it logs what it would do without actually touching the file system. I use this when iterating on new configurations or refactoring existing ones. Combined with the built‑in logging and debug output, it's straightforward to see which resources were scheduled and in which order.</span><br />
<br />
<span>Because RCM is just Ruby, there's no separate agent protocol or daemon. The same process parses the DSL, resolves dependencies, and performs the actions. If something goes wrong, you can drop into the code, add a quick debug statement, and re‑run your configuration.</span><br />
<br />
<h2 style='display: inline' id='rcm-vs-puppet-and-other-big-tools'>RCM vs Puppet and other big tools</h2><br />
<br />
<span>RCM does not try to compete with Puppet, Chef, or Ansible on scale. Those tools shine when you manage hundreds or thousands of machines, have multiple teams contributing modules, and need centralised orchestration, reporting, and role‑based access control. They also come with their own DSLs, servers/agents, certificate handling, and a long list of resource types and modules. Ansible may be more similar to RCM than the other tools, but it's still much more complex than RCM.</span><br />
<br />
<span>For my personal use cases, that layer is mostly overhead. I want:</span><br />
<br />
<ul>
<li>No extra daemon, message bus, or master node.</li>
<li>No separate DSL to learn besides Ruby itself.</li>
<li>A codebase small enough that I can understand and change all of it in an evening.</li>
<li>Behaviour I can inspect just by reading the Ruby code.</li>
</ul><br />
<span>In that space RCM wins: it is small, transparent, and tuned for one person (me!) with a handful of personal machines or my Laptops. I still think tools like Puppet are the right choice for larger organisations and shared infrastructure, but RCM gives me a tiny, focused alternative for my own systems.</span><br />
<br />
<h2 style='display: inline' id='cutting-rcm-010'>Cutting RCM 0.1.0</h2><br />
<br />
<span>As of this post I'm tagging and releasing **RCM 0.1.0**. About 99% of the code has been written by me so far, and before AI agents take over more of the boilerplate and wiring work, it felt like a good moment to cut a release and mark this mostly‑human baseline.</span><br />
<br />
<span>Future changes will very likely involve more automated help, but 0.1.0 is the snapshot of the original, hand‑crafted version of the tool.</span><br />
<br />
<h2 style='display: inline' id='what-s-next'>What's next</h2><br />
<br />
<span>RCM already does what I need on my machines, but there are a few ideas I want to explore:</span><br />
<br />
<ul>
<li>More resource types (for example, services and users) while keeping the core small.</li>
<li>Additional package backends beyond Fedora/DNF (in particular MacOS brew).</li>
<li>Managing hosts remotely.</li>
<li>A slightly more structured way to organise larger configurations without losing the KISS spirit.</li>
</ul><br />
<h2 style='display: inline' id='feature-overview-for-now'>Feature overview (for now)</h2><br />
<br />
<span>Here is a quick overview of what RCM can do today, grouped by area:</span><br />
<br />
<ul>
<li>File management: <span class='inlinecode'>file '/path'</span>, <span class='inlinecode'>manage directory</span>, <span class='inlinecode'>from template</span>, <span class='inlinecode'>line '...'</span></li>
<li>Packages: <span class='inlinecode'>package 'name'</span> resources for installing and updating packages (currently focused on Fedora/DNF)</li>
<li>Conditions and flow: <span class='inlinecode'>given { ... }</span> blocks, predicates such as <span class='inlinecode'>hostname is :earth</span></li>
<li>Notifications and dependencies: <span class='inlinecode'>requires</span> between resources, <span class='inlinecode'>notify</span> for follow‑up actions</li>
<li>Safety and execution modes: backups in <span class='inlinecode'>.rcmbackup/</span>, <span class='inlinecode'>--dry</span> runs, debug logging</li>
</ul><br />
<span>Some small examples adapted from RCM's own tests:</span><br />
<br />
<h3 style='display: inline' id='template-rendering-into-a-file'>Template rendering into a file</h3><br />
<br />
<!-- Generator: GNU source-highlight 3.1.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><font color="#ff0000">configure </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> file </font><font color="#bb00ff">'./.file_example.rcmtmp'</font><font color="#ff0000"> </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> from template</font>
<font color="#ff0000"> </font><font color="#bb00ff">'One plus two is <%= 1 + 2 %>!'</font>
<font color="#ff0000"> </font><b><font color="#ffffff">end</font></b>
<b><font color="#ffffff">end</font></b>
</pre>
<br />
<h3 style='display: inline' id='ensuring-a-line-is-absent-from-a-file'>Ensuring a line is absent from a file</h3><br />
<br />
<!-- Generator: GNU source-highlight 3.1.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><font color="#ff0000">configure </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> file </font><font color="#bb00ff">'./.file_example.rcmtmp'</font><font color="#ff0000"> </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> line </font><font color="#bb00ff">'Whats up?'</font>
<font color="#ff0000"> is absent</font>
<font color="#ff0000"> </font><b><font color="#ffffff">end</font></b>
<b><font color="#ffffff">end</font></b>
</pre>
<br />
<h3 style='display: inline' id='guarding-a-configuration-run-on-the-current-hostname'>Guarding a configuration run on the current hostname</h3><br />
<br />
<!-- Generator: GNU source-highlight 3.1.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><font color="#ff0000">configure </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> given </font><font color="#F3E651">{</font><font color="#ff0000"> hostname Socket</font><font color="#F3E651">.</font><font color="#ff0000">gethostname </font><font color="#F3E651">}</font>
<font color="#ff0000"> </font><font color="#F3E651">...</font>
<b><font color="#ffffff">end</font></b>
</pre>
<br />
<h3 style='display: inline' id='creating-and-deleting-directories-and-purging-a-directory-tree'>Creating and deleting directories, and purging a directory tree</h3><br />
<br />
<!-- Generator: GNU source-highlight 3.1.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><font color="#ff0000">configure </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> directory </font><font color="#bb00ff">'./.directory_example.rcmtmp'</font><font color="#ff0000"> </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> is present</font>
<font color="#ff0000"> </font><b><font color="#ffffff">end</font></b>
<font color="#ff0000"> directory delete </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> path </font><font color="#bb00ff">'./.directory_example.rcmtmp'</font>
<font color="#ff0000"> is absent</font>
<font color="#ff0000"> </font><b><font color="#ffffff">end</font></b>
<b><font color="#ffffff">end</font></b>
</pre>
<br />
<h3 style='display: inline' id='managing-file-and-directory-modes-and-ownership'>Managing file and directory modes and ownership</h3><br />
<br />
<!-- Generator: GNU source-highlight 3.1.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><font color="#ff0000">configure </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> touch </font><font color="#bb00ff">'./.mode_example.rcmtmp'</font><font color="#ff0000"> </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> mode 0o600</font>
<font color="#ff0000"> </font><b><font color="#ffffff">end</font></b>
<font color="#ff0000"> directory </font><font color="#bb00ff">'./.mode_example_dir.rcmtmp'</font><font color="#ff0000"> </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> mode 0o705</font>
<font color="#ff0000"> </font><b><font color="#ffffff">end</font></b>
<b><font color="#ffffff">end</font></b>
</pre>
<br />
<h3 style='display: inline' id='using-a-chained-more-natural-language-style-for-notifications'>Using a chained, more natural language style for notifications</h3><br />
<br />
<span>This will just print out something, not changing anything:</span><br />
<br />
<!-- Generator: GNU source-highlight 3.1.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><font color="#ff0000">configure </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> notify hello dear world </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> thank you to be part of you</font>
<font color="#ff0000"> </font><b><font color="#ffffff">end</font></b>
<b><font color="#ffffff">end</font></b>
</pre>
<br />
<h3 style='display: inline' id='touching-files-and-updating-their-timestamps'>Touching files and updating their timestamps</h3><br />
<br />
<!-- Generator: GNU source-highlight 3.1.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><font color="#ff0000">configure </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> touch </font><font color="#bb00ff">'./.touch_example.rcmtmp'</font>
<b><font color="#ffffff">end</font></b>
</pre>
<br />
<h3 style='display: inline' id='expressing-dependencies-between-notifications'>Expressing dependencies between notifications</h3><br />
<br />
<!-- Generator: GNU source-highlight 3.1.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><font color="#ff0000">configure </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> notify foo </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> requires notify bar </font><b><font color="#ffffff">and</font></b><font color="#ff0000"> requires notify baz</font>
<font color="#ff0000"> </font><font color="#bb00ff">'foo_message'</font>
<font color="#ff0000"> </font><b><font color="#ffffff">end</font></b>
<font color="#ff0000"> notify bar</font>
<font color="#ff0000"> notify baz </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> requires notify bar</font>
<font color="#ff0000"> </font><font color="#bb00ff">'baz_message'</font>
<font color="#ff0000"> </font><b><font color="#ffffff">end</font></b>
<b><font color="#ffffff">end</font></b>
</pre>
<br />
<h3 style='display: inline' id='creating-and-updating-symbolic-links'>Creating and updating symbolic links</h3><br />
<br />
<!-- Generator: GNU source-highlight 3.1.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><font color="#ff0000">configure </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> symlink </font><font color="#bb00ff">'./.symlink_example.rcmtmp'</font><font color="#ff0000"> </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> manage directory</font>
<font color="#ff0000"> </font><font color="#bb00ff">'./.symlink_target_example.rcmtmp'</font>
<font color="#ff0000"> </font><b><font color="#ffffff">end</font></b>
<b><font color="#ffffff">end</font></b>
</pre>
<br />
<h3 style='display: inline' id='detecting-duplicate-resource-definitions-at-configure-time'>Detecting duplicate resource definitions at configure time</h3><br />
<br />
<!-- Generator: GNU source-highlight 3.1.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><font color="#ff0000">configure </font><b><font color="#ffffff">do</font></b>
<font color="#ff0000"> notify </font><font color="#F3E651">:</font><font color="#ff0000">foo</font>
<font color="#ff0000"> notify </font><font color="#F3E651">:</font><font color="#ff0000">foo </font><i><font color="#ababab"># raises RCM::DSL::DuplicateResource</font></i>
<b><font color="#ffffff">end</font></b>
</pre>
<br />
<span>If you find RCM interesting, feel free to browse the code, adapt it to your own setup, or just steal ideas for your own Ruby DSLs. I will probably extend it with more features over time as my own needs evolve.</span><br />
<br />
<span>E-Mail your comments to <span class='inlinecode'>paul@nospam.buetow.org</span> :-)</span><br />
<br />
<span>Other related posts:</span><br />
<br />
<a class='textlink' href='./2026-03-02-rcm-ruby-configuration-management-dsl.html'>2026-03-02 RCM: The Ruby Configuration Management DSL (You are currently reading this)</a><br />
<a class='textlink' href='./2025-10-11-key-takeaways-from-the-well-grounded-rubyist.html'>2025-10-11 Key Takeaways from The Well-Grounded Rubyist</a><br />
<a class='textlink' href='./2021-07-04-the-well-grounded-rubyist.html'>2021-07-04 The Well-Grounded Rubyist</a><br />
<a class='textlink' href='./2016-04-09-jails-and-zfs-on-freebsd-with-puppet.html'>2016-04-09 Jails and ZFS with Puppet on FreeBSD</a><br />
<br />
<a class='textlink' href='../'>Back to the main site</a><br />
<p class="footer">
Generated with <a href="https://codeberg.org/snonux/gemtexter">Gemtexter 3.0.1-develop</a> |
served by <a href="https://www.OpenBSD.org">OpenBSD</a>/<a href="https://man.openbsd.org/relayd.8">relayd(8)</a>+<a href="https://man.openbsd.org/httpd.8">httpd(8)</a> |
<a href="https://foo.zone/site-mirrors.html">Site Mirrors</a>
<br />
Webring: <a href="https://shring.sh/foo.zone/previous">previous</a> | <a href="https://shring.sh">shring</a> | <a href="https://shring.sh/foo.zone/next">next</a>
</p>
<script type="text/javascript" src="../retrofuturistic.js"></script>
</body>
</html>
|