<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title>Terracrypt</title><id>https://www.terracrypt.net/feeds/tags/goblins.xml</id><subtitle>Tag: goblins</subtitle><updated>2026-03-01T20:46:16Z</updated><link href="https://www.terracrypt.net/feeds/tags/goblins.xml" rel="self" /><link href="https://www.terracrypt.net" /><entry><title>Adding persistence to gobs-of-machines</title><id>https://www.terracrypt.net/posts/gobs-of-persistence.html</id><author><name>Jonathan Frederickson</name><email>jonathan@terracrypt.net</email></author><updated>2025-02-14T13:16:00Z</updated><link href="https://www.terracrypt.net/posts/gobs-of-persistence.html" rel="alternate" /><content type="html">&lt;p&gt;A couple updates on the &lt;a href=&quot;/posts/gobs-of-machines.html&quot;&gt;gobs-of-machines&lt;/a&gt; work from a while back!&lt;/p&gt;&lt;p&gt;The smaller update of the two: I've renamed what I was calling &amp;quot;provisioners&amp;quot; to &amp;quot;providers&amp;quot;. This is a bit of an attempt to align my terminology with that of other tools like &lt;a href=&quot;https://developer.hashicorp.com/terraform/language/providers&quot;&gt;Terraform&lt;/a&gt;. Ultimately when deploying real instances I'll need something to install/configure the hob-server component on the new machine, and it might make sense for &lt;em&gt;that&lt;/em&gt; to be called a &amp;quot;provisioner&amp;quot;. So I'm calling the part that interacts with cloud providers a &amp;quot;provider&amp;quot;. So much of this terminology is overloaded in devops tooling, but so it goes. :)&lt;/p&gt;&lt;p&gt;But the bigger change: the program now supports persistence! In practice, this means that you can shut down and restart the program (primarily the &lt;code&gt;boss&lt;/code&gt;), and when it comes back online it will remember the other instances that it's created.&lt;/p&gt;&lt;p&gt;This took a bit of troubleshooting (the errors you get when you forget something currently aren't as clear as they could be) but ultimately I was pleasantly surprised at how few changes I had to do to add persistence support!&lt;/p&gt;&lt;p&gt;The &lt;a href=&quot;https://files.spritely.institute/docs/guile-goblins/latest/Persistence.html&quot;&gt;Goblins docs on persistence&lt;/a&gt; explain this in more detail than I can here, but the changes mostly boiled down to:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Defining persistence environments for each custom object (and their dependencies)&lt;/li&gt;&lt;li&gt;Using &lt;code&gt;define-actor&lt;/code&gt; instead of &lt;code&gt;define&lt;/code&gt; for each of my objects&lt;/li&gt;&lt;li&gt;Pulling some of the variable definitions within each object into the object's constructor as optional parameters&lt;/li&gt;&lt;li&gt;Spawning my objects in persistent vats&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Walking through these:&lt;/p&gt;&lt;h2&gt;Defining persistence environments&lt;/h2&gt;&lt;p&gt;Each module that defines new objects needs to define a &amp;quot;persistence environment&amp;quot;. For &lt;code&gt;hob.scm&lt;/code&gt;, that looks like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot;&gt;(define hob-env
  (make-persistence-env
   `((((gobs-of-machines hob) ^hob-server-presence) ,^hob-server-presence)
     (((gobs-of-machines hob) ^hob-client-presence) ,^hob-client-presence))
   #:extends common-env))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This gives Goblins some information it needs to snapshot and rehydrate objects: the module that the object is found in, the name of that object's constructor within that module, and the &amp;quot;object rehydrator&amp;quot; - which seems to be a direct reference to the object's constructor.&lt;/p&gt;&lt;p&gt;We extend another persistence env, &lt;code&gt;common-env&lt;/code&gt;, because the hob-client uses a &lt;code&gt;ghash&lt;/code&gt; to store bindings. So we need to tell Goblins how to persist &lt;code&gt;ghash&lt;/code&gt;es too, and &lt;code&gt;(goblins actor-lib common)&lt;/code&gt; provides a persistence env for the objects defined there that we can inherit from.&lt;/p&gt;&lt;h2&gt;Using &lt;code&gt;define-actor&lt;/code&gt; and pulling more into object constructors&lt;/h2&gt;&lt;p&gt;Goblins provides a macro called &lt;code&gt;define-actor&lt;/code&gt; that handles a lot of the persistence machinery for us. Using it in practice for e.g. the &lt;code&gt;hob-client-presence&lt;/code&gt; meant changing from this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot;&gt;(define (^hob-client-presence bcom)
  (define bindings (spawn ^ghash))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...to this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot;&gt;(define-actor (^hob-client-presence bcom #:optional (bindings (spawn ^ghash)))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Instead of using an inner definition, I'm defining the &lt;code&gt;bindings&lt;/code&gt; hash table as an optional parameter to the object constructor, with its default value set to a newly spawned &lt;code&gt;ghash&lt;/code&gt;. To quote the Goblins manual:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;In most cases, and usually when using ‘define-actor’, we need to think the behavior and state being determined by the constructor, not relying on internal state.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;When the program starts, Goblins will recreate each object by spawning a new object with the same parameters, so moving state like this into the constructor allows it to recreate the previous state of the object.&lt;/p&gt;&lt;h2&gt;Using persistent vats&lt;/h2&gt;&lt;p&gt;This was the hardest part to work out.&lt;/p&gt;&lt;p&gt;To make use of Goblins's persistence features, you need to spawn your objects inside special vats that are persisted. The storage mechanism is pluggable, but for the time being the only one that actually saves to disk is the &lt;code&gt;syrup-store&lt;/code&gt;, so for now I'm using that. Spawning a &lt;code&gt;syrup-store&lt;/code&gt; looks like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot;&gt;(define dummy-vat-store (make-syrup-store &amp;quot;/tmp/gobs-dummy-state&amp;quot;))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If I only had one vat to deal with, this (along with the persistence environment stuff mentioned in an earlier section) would be all that I would need to spawn a persistent vat. However, I had previously designed the application such that the &lt;code&gt;dummy-machine&lt;/code&gt; and &lt;code&gt;hob-client&lt;/code&gt; objects lived in a separate vat, to roughly simulate the fact that in reality these would be running on the newly provisioned machine.&lt;/p&gt;&lt;p&gt;Goblins persists one vat at a time, so what do you do when you have objects in two different vats holding references to each other? Well, there is a mechanism for that too - you can make use of something called a &lt;code&gt;persistence-registry&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot;&gt;(define persistence-vat (spawn-vat))
(define persistence-registry
  (with-vat persistence-vat
            (spawn ^persistence-registry)))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And then each time you spawn a new persistent vat, you pass a reference to the persistence registry into the &lt;code&gt;spawn-persistent-vat&lt;/code&gt; call:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot;&gt;(define-values (dummy-vat dummy-provider)
  (spawn-persistent-vat
   dummy-env
   (lambda ()
     (spawn ^dummy-provider))
   dummy-vat-store
   #:persistence-registry persistence-registry))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;One noteworthy thing about &lt;code&gt;spawn-persistent-vat&lt;/code&gt; is that when using it, you spawn the first object in the vat at the same time as the vat itself. This first object acts as the &amp;quot;root&amp;quot; of the persistence graph, and for other objects in this vat to be persisted, this first object needs to hold a reference to them. (This will be important later.)&lt;/p&gt;&lt;p&gt;Also, If you're paying close attention, you may notice that this isn't where the &lt;code&gt;dummy-provider&lt;/code&gt; was previously spawned, and there's a reason for that.&lt;/p&gt;&lt;p&gt;Previously, I was creating a new vat for each new machine, and this became a problem, because vats aren't in the list of data types that can appear in a persistence graph! (Even persistent vats, it seems - though I wonder if that could be supported at some point or if there are hurdles to implementing that.)&lt;/p&gt;&lt;p&gt;So I had to refactor a bit. Now, the &lt;code&gt;boss&lt;/code&gt; takes an extra &lt;code&gt;providers&lt;/code&gt; parameter, which is a &lt;code&gt;ghash&lt;/code&gt; whose keys are each the provider name and the values are references to a provider object:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot;&gt;(define-actor (^boss bcom #:key [providers (spawn ^ghash)] [machines (spawn ^ghash)])&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Essentially I'm sidestepping the above limitation by spawning the &lt;code&gt;dummy-provider&lt;/code&gt; in a new persistent vat first, and then passing it into the &lt;code&gt;boss&lt;/code&gt; as an argument (with the &lt;code&gt;dummy-provider&lt;/code&gt; modified to spawn each new &lt;code&gt;dummy-machine&lt;/code&gt; in the same vat rather than spawning a new one each time).&lt;/p&gt;&lt;p&gt;And then, having done that, we can spawn the persistent vat our &lt;code&gt;boss&lt;/code&gt; will live in:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot;&gt;(define syrup-store (make-syrup-store &amp;quot;/tmp/gobs-state&amp;quot;))
(define-values (my-vat my-boss)
  (spawn-persistent-vat
   boss-env
   (lambda ()
     (let* ((ht (spawn ^ghash)))
       ($ ht 'set 'dummy dummy-provider)
       (spawn ^boss #:providers ht)))
   syrup-store
   #:persistence-registry persistence-registry))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, the thing that had me scratching my head for a while had to do with a little quirk of that vat independence I mentioned earlier, as well as with the requirement that the root retain a reference to objects in that vat that will be persisted. Previously, when creating a new machine, the &lt;code&gt;hob-server&lt;/code&gt; for the new machine would ultimately get a reference to the new &lt;code&gt;hob-client&lt;/code&gt;. But the provider wasn't keeping a reference to that newly created &lt;code&gt;hob-client&lt;/code&gt; anywhere, and so when rehydrating the object graph it didn't come back.&lt;/p&gt;&lt;p&gt;Fixing this just involved &lt;a href=&quot;https://git.sr.ht/~jfred/gobs-of-machines/commit/a21407d7f41ea1e774e0572076eb52344945e2b2&quot;&gt;making the &lt;code&gt;dummy-machine&lt;/code&gt; object persist its &lt;code&gt;hob-client&lt;/code&gt; and making the &lt;code&gt;dummy-provider&lt;/code&gt; persist each of the &lt;code&gt;dummy-machine&lt;/code&gt;s&lt;/a&gt;, but it took a little bit of puzzling to figure out.&lt;/p&gt;&lt;h2&gt;Persistence in action&lt;/h2&gt;&lt;p&gt;Phew! Having made those changes, persistence now works:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;scheme@(gobs-of-machines main)&amp;gt; (define-values (vat boss) (start-daemon))
scheme@(gobs-of-machines main)&amp;gt; ,enter-vat vat
Entering vat '2'.  Type ',q' to exit.  Type ',help goblins' for help.
goblins/2@(gobs-of-machines main) [1]&amp;gt; ($ boss 'create-machine &amp;quot;test&amp;quot;)
dummy-provider: In a real implementation, this would talk to a cloud provider
goblins/2@(gobs-of-machines main) [1]&amp;gt; dummy-machine: Creating new machine
dummy: Registered new client machine

goblins/2@(gobs-of-machines main) [1]&amp;gt; ($ boss 'list-machines)
$6 = (&amp;quot;test&amp;quot;)

&amp;lt;...then after killing and restarting the repl...&amp;gt;

scheme@(gobs-of-machines main)&amp;gt; (define-values (vat boss) (start-daemon))
scheme@(gobs-of-machines main)&amp;gt; ,enter-vat vat
Entering vat '2'.  Type ',q' to exit.  Type ',help goblins' for help.
goblins/2@(gobs-of-machines main) [1]&amp;gt; ($ boss 'list-machines)
$6 = (&amp;quot;test&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Success!&lt;/p&gt;&lt;p&gt;Obviously, there are still a couple tweaks that need to be made before this is really useful as a standalone application. There are some hardcoded &lt;code&gt;/tmp&lt;/code&gt; filenames lying around for the two persistent vats; the location for those needs to be configurable. It still needs a command-line entrypoint and a config file, so you can start and configure it from outside a REPL. And it still needs at least one real provider implementation! But it's a start. :)&lt;/p&gt;</content></entry><entry><title>Brainstorming - managing a fleet of Guix machines with Goblins</title><id>https://www.terracrypt.net/posts/guix-goblins-fleet.html</id><author><name>Jonathan Frederickson</name><email>jonathan@terracrypt.net</email></author><updated>2024-12-19T17:04:00Z</updated><link href="https://www.terracrypt.net/posts/guix-goblins-fleet.html" rel="alternate" /><content type="html">&lt;p&gt;I've been noodling on the possibility of managing a bunch of Guix machines with Goblins for a little while now. Spent a little time at a local coffee shop today thinking about how this might work...&lt;/p&gt;&lt;p&gt;With both Guix and Goblins being written in Guile, it's at least pretty straightforward to start mashing them up:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot;&gt;(use-modules
 (goblins)
 (goblins actor-lib methods)
 (guix describe))

(define (^guix-machine-mgr bcom)
  (methods
   [(get-channels)
    (current-channels)]))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Of course, functions like &lt;code&gt;current-channels&lt;/code&gt; return Guix records...&lt;/p&gt;&lt;pre&gt;&lt;code&gt;goblins/0@(guile-user) [1]&amp;gt; ($ mgr 'get-channels)
$6 = (#&amp;lt;&amp;lt;channel&amp;gt; name: guix url: &amp;quot;https://git.savannah.gnu.org/git/guix.git&amp;quot; branch: &amp;quot;master&amp;quot; commit: &amp;quot;5ab3c4c1e43ebb637551223791db0ea3519986e1&amp;quot; introduction: #&amp;lt;&amp;lt;channel-introduction&amp;gt; first-signed-commit: &amp;quot;9edb3f66fd807b096b48283debdcddccfea34bad&amp;quot; first-commit-signer: #vu8(187 176 45 223 44 234 246 168 13 29 230 67 162 160 109 242 163 58 84 250)&amp;gt; location: ((filename . &amp;quot;guix/channels.scm&amp;quot;) (line . 1069) (column . 5))&amp;gt;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...and I don't know yet whether these can be sent over a captp connection as-is or if I'll need to wrap them somehow. Gotta play around I guess!&lt;/p&gt;&lt;p&gt;One of the other things I haven't decided on yet is which parts of a Guix system exactly this should manage, since Guix lets multiple users have multiple profiles, and since the currently configured channels for a given user don't necessarily reflect the currently installed packages in the user's profile. It might make sense for such a Goblins daemon to take a specific profile as a parameter, and to essentially take ownership of that profile.&lt;/p&gt;&lt;p&gt;I'd also like it to be able to remotely manage the operating-system config and by extension the packages/services installed through it, though I'll need to figure out how exactly to do that. &lt;code&gt;switch-to-system&lt;/code&gt; in &lt;code&gt;(guix scripts system reconfigure)&lt;/code&gt; seems like it might be the right starting point.&lt;/p&gt;&lt;p&gt;My current (very rough) thought on what the management flow would look like is:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;On a centralized management machine, have an object per client machine that represents the desired state of that machine, with a reference to the following daemon&lt;/li&gt;&lt;li&gt;On each client machine, have a daemon running that's managing the operating-system config on that machine, with a reference to the above object&lt;/li&gt;&lt;li&gt;When an admin updates the list of packages/services on the machine object, it sends a message to the client daemon prompting it to run a system rebuild&lt;/li&gt;&lt;li&gt;Periodically, the client daemon reaches out to the machine object to determine if it is up to date (e.g. if it's missed one of the above messages), and to fetch the current desired config if not&lt;/li&gt;&lt;/ul&gt;</content></entry></feed>