<?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/da2024.xml</id><subtitle>Tag: da2024</subtitle><updated>2026-03-01T20:46:16Z</updated><link href="https://www.terracrypt.net/feeds/tags/da2024.xml" rel="self" /><link href="https://www.terracrypt.net" /><entry><title>Prototyping a machine deployment tool with Spritely Goblins</title><id>https://www.terracrypt.net/posts/gobs-of-machines.html</id><author><name>Jonathan Frederickson</name><email>jonathan@terracrypt.net</email></author><updated>2025-01-07T12:10:00Z</updated><link href="https://www.terracrypt.net/posts/gobs-of-machines.html" rel="alternate" /><content type="html">&lt;p&gt;Wrapping up December Adventure for this year and related to &lt;a href=&quot;/posts/guix-goblins-fleet.html&quot;&gt;my last post&lt;/a&gt;, here's some early prototyping I did over the holidays of a machine management tool. After I posted the last post, I had a conversation with some of my friends in which it was difficult to convey exactly how this might work, so I'm writing this up partly to serve as an explainer for my thought process.&lt;/p&gt;&lt;p&gt;In this post I may assume some basic familiarity with &lt;a href=&quot;/garden/ocap.html&quot;&gt;object capabilities&lt;/a&gt; and &lt;a href=&quot;https://spritely.institute/goblins/&quot;&gt;Spritely Goblins&lt;/a&gt;, though I'll try to explain things to some degree as I go.&lt;/p&gt;&lt;h2&gt;Goals&lt;/h2&gt;&lt;p&gt;From a high level, what I'd like to accomplish is something like: you have a centralized infra management tool that's responsible for provisioning new machines. When you want to create a new machine, you send a message to that management tool, and the response you get back is a reference to some management interface for that new machine.&lt;/p&gt;&lt;p&gt;Crucially, this system must effectively avoid the &lt;em&gt;trust bootstrapping problem&lt;/em&gt;; that is to say, the new instance must be able to securely connect to the management tool, and the management tool must have confidence that the new instance is what just connected to it. Despite seeming simple, this has been surprisingly difficult to accomplish with common devops tools in my experience! It can be done, but often either is not or takes a lot of work. So that's something I'd like to address right out of the gate.&lt;/p&gt;&lt;p&gt;Furthermore, I'd like to make it straightforward to connect services running on different machines to each other. Even when you have a single deployment tool deploying two different services, it's often more involved than you would hope to get those two services talking to each other. Consider the example of a web application and a database instance deployed specifically for that web application. You've deployed the web application onto one machine, and the database instance on another, and you need the web application to connect to the database. What do you need for this? Typically, at least:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The ability to generate login credentials to the database for the web application to use&lt;/li&gt;&lt;li&gt;The ability to modify the web application's configuration to use said database instance&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In my experience, both of those steps are often done semi-manually; often someone will manually generate database credentials to use, and while the configuration of the webapp will be config managed, it'll pull those static database credentials from some source (maybe it's a &lt;a href=&quot;https://getsops.io/&quot;&gt;sops&lt;/a&gt;-encrypted file, maybe it's in a secrets manager somewhere, etc). When it is done automatically via something like &lt;a href=&quot;https://developer.hashicorp.com/vault/api-docs/secret/databases/mysql-maria&quot;&gt;Vault&lt;/a&gt;, this relies on that separate tool with its own policies, and bootstrapping client trust to Vault is itself a whole rabbit hole!&lt;/p&gt;&lt;p&gt;Wouldn't it be nice if your provisioning tool could take care of this kind of thing for you?&lt;/p&gt;&lt;h2&gt;Building a prototype&lt;/h2&gt;&lt;p&gt;I've built a basic prototype of some of what I have in mind called &lt;a href=&quot;https://git.sr.ht/~jfred/gobs-of-machines&quot;&gt;gobs-of-machines&lt;/a&gt;. I'll probably work on it (and document it) more in the future, but for now I'd like to go over what I have so far.&lt;/p&gt;&lt;p&gt;The system I've built has a few parts, most with D&amp;amp;D-esque names. There's the &lt;strong&gt;boss&lt;/strong&gt;, which is responsible for triggering the provisioning process of new machines and keeping track of the machines it's provisioned. There are &lt;strong&gt;provisioners&lt;/strong&gt;, which are what would call out to the APIs of a given cloud provider and provision a new machine with some specified user data. And then there's the &lt;strong&gt;hob&lt;/strong&gt;, a service in two parts - one colocated with the boss, and another running on each deployed machine. This acts as the communication channel between the boss and each machine. (Named after the &lt;a href=&quot;https://en.wikipedia.org/wiki/Hob_(folklore)&quot;&gt;household spirit or hobgoblin&lt;/a&gt;, not the &lt;a href=&quot;https://en.wiktionary.org/wiki/hob#Noun&quot;&gt;stove&lt;/a&gt;. Maybe I'll rename it if it gets too confusing, but hobgoblin is pretty verbose. Names, ya know!)&lt;/p&gt;&lt;h3&gt;Boss&lt;/h3&gt;&lt;p&gt;Starting with the boss, we'll create a Goblins object with a hashtable inside it. This will map a human-readable name for each machine to an object we can use to communicate with that machine.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;(define (^boss bcom)
  (define machines (spawn ^ghash))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For the time being, since I haven't implemented any cloud provider provisioners, we'll use a dummy provisioner as a placeholder. In a real implementation we'd want a more sophisticated way to manage provisioners for multiple providers, but for now let's just create an instance of the dummy provisioner inside the boss object.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;  (define dummy-provisioner (spawn ^dummy-provisioner))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We'll add a getter methods to get a specific machine from the hashtable by name, and a list method to list all the machines that have been registered:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;  (methods
   [(get-machine name)
    ($ machines 'ref name)]
   [(list-machines)
    (ghash-fold ghash-keys '() ($ machines 'data))]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For those coming from other object oriented programming languages, this will hopefully feel somewhat familiar. We're defining the constructor for an object (in Goblins's convention, the &lt;code&gt;^&lt;/code&gt; at the beginning of names like &lt;code&gt;^boss&lt;/code&gt; denotes a constructor) and defining some methods for that object. There are a few Goblins-specific quirks to explain here:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The &lt;code&gt;$&lt;/code&gt; in the &lt;code&gt;get-machine&lt;/code&gt; method stands for a synchronous method call on another Goblins object (with some transactional functionality that I won't get into yet)&lt;/li&gt;&lt;li&gt;The implementation of the &lt;code&gt;list-machines&lt;/code&gt; method is a bit funky, because Goblins's &lt;code&gt;ghash&lt;/code&gt; objects don't have a built-in method to list all the keys in the hashtable. So what I'm doing here is grabbing the underlying hashtable and pulling out the keys from it. (&lt;code&gt;ghash-keys&lt;/code&gt; is a procedure whose implementation I've elided for brevity, check &lt;a href=&quot;https://git.sr.ht/~jfred/gobs-of-machines/tree/master/item/gobs-of-machines/boss.scm&quot;&gt;the source&lt;/a&gt; if you're curious.)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;One thing worth noting about Goblins programming is that, unlike some other object oriented programming languages like Python, you should generally think of method calls as messages sent between objects. It's a very &lt;a href=&quot;https://softwareengineering.stackexchange.com/a/58732&quot;&gt;Alan Kay-style object system&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Okay, now for the only particularly interesting part of the boss, the &lt;code&gt;create-machine&lt;/code&gt; method:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;   [(create-machine name)
    (define hob-server (spawn ^hob-server-presence))
    (on (&amp;lt;- dummy-provisioner 'new-machine hob-server) ;; TODO selectable provisioners
        (lambda (ret)
          ($ machines 'set name hob-server)))]))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This one adds a couple new things to go over. Within the body of the &lt;code&gt;create-machine&lt;/code&gt; method, we're spawning one of the components of the &lt;code&gt;hob&lt;/code&gt; service - the one that lives alongside the &lt;code&gt;boss&lt;/code&gt;. We're then sending an asynchronous message to the provisioner (that's what &lt;code&gt;&amp;lt;-&lt;/code&gt; does) and passing it a reference to the &lt;code&gt;hob&lt;/code&gt; object that we created.&lt;/p&gt;&lt;p&gt;Async messages, as in some other languages like JavaScript, return promises that will be fulfilled later rather than waiting for the response to come back. In Goblins, you can trigger an action when a promise is resolved with &lt;code&gt;on&lt;/code&gt;. In this case, when the promise is resolved (i.e. when the boss gets a response from the provisioner), we add an entry to the hashtable created above for the new server. Currently I have it waiting until after hearing back from the provisioner to avoid adding entries for new nodes if provisioning fails, but we could just as well create a &amp;quot;pending&amp;quot; entry in the hashtable and update it when provisioning succeeds.&lt;/p&gt;&lt;h3&gt;Provisioners&lt;/h3&gt;&lt;p&gt;Now that we've seen the code that kicks off the provisioning process, let's take a look at what the provisioner itself does. For now I only have a dummy provisioner with one method to try out the idea:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;(define (^dummy-provisioner bcom)
  (methods
   [(new-machine hob-server-presence)
    (display &amp;quot;dummy-provisioner: In a real provisioner, this would talk to a cloud provider\n&amp;quot;)
    (let ((new-vat (spawn-vat)))
      (with-vat new-vat
                (define machine (spawn ^dummy-machine))
                ($ machine 'provision hob-server-presence)))]))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is the method of the provisioner that we saw the boss call earlier.&lt;/p&gt;&lt;p&gt;Part of this again deserves some explanation, because it's Goblins-specific. Because we're not actually provisioning a new VM at this stage, I'm instead spawning a new &amp;quot;vat&amp;quot; to run some extra code in. A vat is Goblins's mechanism of concurrency; it's an event loop containing objects. Objects within the same vat can make synchronous method calls between each other, while objects in different vats can only send asynchronous method calls. Objects on separate machines can communicate with each other via CapTP, but they will necessarily be in different vats and so can only communicate asynchronously.&lt;/p&gt;&lt;p&gt;Running part of this code in a separate vat is my attempt to simulate running on a separate machine, though of course there are a few things that would be different in a real provisioner:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The provisioner must be able to deploy some code to the new machine. There's a client component that needs to run on the new machine for this all to work.&lt;/li&gt;&lt;li&gt;Crucially, the provisioner must be able to pass the hob-server reference to the new machine.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The latter is trivial in the dummy provisioner case, when everything is running on the same machine, but Goblins has a trick up its sleeve that makes it possible across machines. If you have OCapN set up, you can &lt;a href=&quot;https://files.spritely.institute/docs/guile-goblins/latest/Using-the-CapTP-API.html&quot;&gt;serialize the reference to the hob-server reference as a sturdyref&lt;/a&gt; so you can send it over the network as a string. In a cloud provider that supports user data (&lt;a href=&quot;https://techdocs.akamai.com/cloud-computing/docs/overview-of-the-metadata-service&quot;&gt;Linode, for example&lt;/a&gt;), you can include the sturdyref in user data for the new instance. The code running on the remote machine can then enliven the sturdyref, turning it back into a live reference, after which point objects on each machine can send asynchronous messages to each other as usual.&lt;/p&gt;&lt;p&gt;The dummy provisioning code that runs in the new vat doesn't do a whole lot; it looks like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot;&gt;(define (^dummy-machine bcom)
  (methods
   [(provision hob-server-presence)
    (display &amp;quot;dummy: Provisioning new machine\n&amp;quot;)
    (let ((client (spawn ^hob-client-presence)))
      (on (&amp;lt;- hob-server-presence 'register client)
          (lambda (ret)
            (display &amp;quot;dummy: Registered new client machine\n&amp;quot;))))]))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Essentially, it just spawns a new instance of a &lt;code&gt;hob-client-presence&lt;/code&gt; on the new machine, registers it with the associated hob server, and logs that it's done so. More on what that does in a minute. In a real provisioner, this might also be where we set up persistence for the &lt;code&gt;hob-client-presence&lt;/code&gt; object, because we'll want that to stick around across reboots.&lt;/p&gt;&lt;h3&gt;Hobs&lt;/h3&gt;&lt;p&gt;So I've shown how you start the provisioning process, and I've shown what a trivial provisioner might do. That covers some initial provisioning, but what does the interface to machines after that point look like? That's the job of the &lt;code&gt;hob&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;As I mentioned before, the &lt;code&gt;hob&lt;/code&gt; is a service in two parts: one that lives on the management server (alongside the &lt;code&gt;boss&lt;/code&gt;, one instance per machine), and one that lives on each machine. As with the other components of this system, these are implemented as Goblins objects.&lt;/p&gt;&lt;p&gt;We saw above that the one thing the machine-side component did was to register the hob-client with its associated server. Let's take a look at the server:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot;&gt;(define-actor (^hob-server-presence bcom #:optional (client #f))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each machine gets a hob-server instance, and each hob-server instance will communicate with its associated hob-client instance. The hob-server therefore needs to know where its hob-client is. So we'll pass in the client as an optional parameter, defaulting to false initially.&lt;/p&gt;&lt;p&gt;The next part has a few Goblins-isms that deserve some explanation:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot;&gt;  (define pending-beh
    (methods
     [(register client-presence)
      (bcom (^hob-server-presence bcom client-presence))]))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We're using &lt;code&gt;methods&lt;/code&gt; like before, only now we're assigning it to a variable. What? Well, it turns out &lt;code&gt;methods&lt;/code&gt; isn't a special, core part of the language - it's a macro that emits a function dispatching on a method name as its first argument. And because it emits a function, we can assign that function to a variable as we would anything else. We'll see why this is useful in a bit.&lt;/p&gt;&lt;p&gt;The other oddity here is the last line in the &lt;code&gt;register&lt;/code&gt; method - &lt;code&gt;(bcom (^hob-server-presence bcom client-presence))&lt;/code&gt;. I didn't explain it above, but you may have noticed that each actor has &lt;code&gt;bcom&lt;/code&gt; in its argument list. This is a capability that allows an object to &amp;quot;become&amp;quot; something else; it lets Goblins objects act as a sort of state machine. If the last thing that an object does on a method call is &lt;code&gt;bcom&lt;/code&gt; something else, then the next time you make a call to that object, it'll use the behavior of that new thing instead of its original behavior. Here, the argument to &lt;code&gt;bcom&lt;/code&gt; is the constructor for a new &lt;code&gt;hob-server-presence&lt;/code&gt;, but this time with a client defined.&lt;/p&gt;&lt;p&gt;When we spawn a new &lt;code&gt;hob-server&lt;/code&gt; initially, it doesn't know where its client is! It can't at that point, because the machine it's on hasn't been provisioned yet. Only after the new machine has been provisioned and its &lt;code&gt;hob-client&lt;/code&gt; has been spawned can the server possibly know where it is. So we initially create the &lt;code&gt;hob-server&lt;/code&gt; in a sort of pending state, where it's just sitting there waiting for a client to register to it. Only after a client registers does it transition to its active behavior. And what is its active behavior?&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot;&gt;  (define active-beh
    (methods
     [(register-binding name svc-cap)
      (&amp;lt;- client 'register-binding name svc-cap)]
     [(list-bindings)
      (&amp;lt;- client 'list-bindings)]
     [(get-binding name)
      (&amp;lt;- client 'get-binding name)]))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For now, it only does three things: it lets you register a service binding, list the already registered service bindings, and get a service binding by name. Each of these method calls is passed through directly to the underlying &lt;code&gt;hob-client&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;What is a service binding? Inspired by &lt;a href=&quot;https://blog.cloudflare.com/workers-environment-live-object-bindings/&quot;&gt;this CloudFlare Workers blog post&lt;/a&gt;, a binding is a mapping between a human-readable name (something like &lt;code&gt;db&lt;/code&gt; for example) and a service capability (which is currently any arbitrary capability, but it may be better to have a defined interface for these capabilities later on). This gives each machine in the system its own namespace for looking up services. For example, an application could be configured to look up and connect to the &lt;code&gt;db&lt;/code&gt; service, and exactly which service that ends up connecting to depends on the bindings available on that machine.&lt;/p&gt;&lt;p&gt;Lastly, the &lt;code&gt;hob-server&lt;/code&gt; needs an initial behavior to start with. The client was an optional parameter, so we'll choose the active behavior if that parameter is truthy (which it would be if we've already registered a client) or the pending behavior if it's false:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot;&gt;  (if client
      active-beh
      pending-beh))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now for the client side. Its implementation is pretty similar to the &lt;code&gt;boss&lt;/code&gt; as it turns out, because it's doing a very similar thing - mapping from a human-readable name to something stored in a hash table:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot;&gt;(define (^hob-client-presence bcom)
  (define bindings (spawn ^ghash))
  (methods
   [(register-binding name svc-cap)
    ($ bindings 'set name svc-cap)]
   [(list-bindings)
    (ghash-fold ghash-keys '() ($ bindings 'data))]
   [(get-binding name)
    ($ bindings 'ref name)]))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(An aside on naming - you might be wondering why the hob-client and hob-server are called &amp;quot;presences&amp;quot;. The terminology, and the rough outlines of this architecture, come from something known in the ocap world as &lt;a href=&quot;http://habitatchronicles.com/2019/08/the-unum-pattern/&quot;&gt;the unum pattern&lt;/a&gt;.)&lt;/p&gt;&lt;h2&gt;Putting it all together&lt;/h2&gt;&lt;p&gt;Now that we have all this in place, let's think a bit about what this allows us to do. Imagine that we've created two machines, say, &lt;code&gt;app-machine&lt;/code&gt; and &lt;code&gt;db-machine&lt;/code&gt;. On &lt;code&gt;db-machine&lt;/code&gt;, imagine that we're running a database service (and assume for a moment that the database service itself speaks CapTP for simplicity's sake). On the database server, you could run:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;($ hob-client 'register-binding &amp;quot;db&amp;quot; db-cap) ;; running on db-machine&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You could then do this, from the boss:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;(define db-machine ($ boss 'get-machine &amp;quot;db-machine&amp;quot;))
(define app-machine ($ boss 'get-machine &amp;quot;app-machine&amp;quot;))
(define db-cap (&amp;lt;- db-machine 'get-binding &amp;quot;db&amp;quot;))
(&amp;lt;- app-machine 'register-binding &amp;quot;db&amp;quot; db-cap)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, &lt;code&gt;app-machine&lt;/code&gt; has access to the DB service via its capability!&lt;/p&gt;&lt;pre&gt;&lt;code&gt;($ hob-client 'get-binding &amp;quot;db&amp;quot;) ;; running on app-machine&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Of course, there aren't any database services that speak CapTP yet that I know of, but you can see the above flow working if you define &lt;code&gt;db-cap&lt;/code&gt; to be something like a &lt;code&gt;ghash&lt;/code&gt; for testing purposes.&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;This is still a rough prototype, and there's lots of details I haven't covered yet. For example, you may want to &lt;a href=&quot;https://en.wikipedia.org/wiki/Object-capability_model#Glossary_of_related_terms&quot;&gt;attenuate&lt;/a&gt; the capability to a service exposed by one machine before passing it on to another. And because most existing services do not natively speak CapTP, you'd need to write a capability-style wrapper for them. This may be more or less difficult, depending on the service in question, and supporting attenuation in particular in a wrapper is likely to be a challenge.&lt;/p&gt;&lt;p&gt;I also haven't defined a declarative interface for passing service references between machines. That's possible to build on top of these foundations, but is definitely a lot more involved than what I've shown so far. For long-term administrative tasks, you may want to be able to define a service graph (i.e. &amp;quot;service db on db-machine points to service db on app-machine&amp;quot;) and have the system transfer those capabilities between the machines automatically.&lt;/p&gt;&lt;p&gt;But rough as it is, I hope this has given you some insight into the things that are possible to build in a capability framework. I know in my professional life there have been many times when I've wished connecting services to each other were a lot more straightforward, and so I wanted to take a crack at showing what's possible. Hopefully I've at least somewhat succeeded. :)&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><entry><title>Migrated the website for ActivityPub support</title><id>https://www.terracrypt.net/posts/ap-migration.html</id><author><name>Jonathan Frederickson</name><email>jonathan@terracrypt.net</email></author><updated>2024-12-10T21:55:00Z</updated><link href="https://www.terracrypt.net/posts/ap-migration.html" rel="alternate" /><content type="html">&lt;p&gt;Migrated the website back to a server I control so I can set the Content-Type header properly on ActivityPub objects. And it looks like that was enough for Mastodon to be able to pull up my profile!&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;/images/website-profile.png&quot; alt=&quot;This website showing up as a profile in the Mastodon UI&quot; /&gt;&lt;/p&gt;&lt;p&gt;So that's a nice start. I guess past this point it's a matter of:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Adding some of the missing metadata to make the profile look nice, and...&lt;/li&gt;&lt;li&gt;Building a separate daemon to actually handle ActivityPub follows and broadcasts. I assume I can have that daemon just periodically check my outbox for new posts and broadcast them to followers.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;But the static site part of this has all been pretty straightforward so far, which has been nice!&lt;/p&gt;</content></entry><entry><title>Art break!</title><id>https://www.terracrypt.net/posts/art-break.html</id><author><name>Jonathan Frederickson</name><email>jonathan@terracrypt.net</email></author><updated>2024-12-04T20:48:00Z</updated><link href="https://www.terracrypt.net/posts/art-break.html" rel="alternate" /><content type="html">&lt;p&gt;Taking a little break today from coding for an artistic break - I'm trying to turn an existing image into a vector design for an enamel pin. I think it came out pretty well!&lt;/p&gt;&lt;p&gt;Original image:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;/images/goblins.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;p&gt;My first attempt at a vectorization with a small color palette:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;/images/goblins-pin-v1.svg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;p&gt;Original image was taken from the &lt;a href=&quot;https://spritely.institute/&quot;&gt;Spritely website&lt;/a&gt; and is © Spritely Networked Communities Institute, released under &lt;a href=&quot;https://creativecommons.org/licenses/by/4.0/&quot;&gt;CC BY 4.0&lt;/a&gt;. Vectorized version is released under the same license.&lt;/p&gt;</content></entry><entry><title>Tag pages!</title><id>https://www.terracrypt.net/posts/tag-pages.html</id><author><name>Jonathan Frederickson</name><email>jonathan@terracrypt.net</email></author><updated>2024-12-02T21:27:00Z</updated><link href="https://www.terracrypt.net/posts/tag-pages.html" rel="alternate" /><content type="html">&lt;p&gt;Same-day post I know, but I just managed to hack together per-tag page support for this site! I love that Haunt makes things like this so easy. Currently only the &lt;a href=&quot;/posts/tags/da2024.html&quot;&gt;da2024&lt;/a&gt; page exists, but I'll be generating pages for all of them soon.&lt;/p&gt;&lt;p&gt;The RSS links all still go to the global feed, but I do have per-tag feeds already, so... I'll fix that at some point too, haha. (And I guess I should actually show the tags that each post has somewhere on the post page too. Details!)&lt;/p&gt;</content></entry><entry><title>December Adventure 2024</title><id>https://www.terracrypt.net/posts/december-adventure-2024.html</id><author><name>Jonathan Frederickson</name><email>jonathan@terracrypt.net</email></author><updated>2024-12-02T12:45:00Z</updated><link href="https://www.terracrypt.net/posts/december-adventure-2024.html" rel="alternate" /><content type="html">&lt;p&gt;I heard about &lt;a href=&quot;https://eli.li/december-adventure&quot;&gt;December Adventure&lt;/a&gt; today, and thought it was a great idea! Since I have a project in mind to work on at the moment (that I was working on yesterday anyway), I think I'll participate. :)&lt;/p&gt;&lt;p&gt;Christine Lemmer-Webber &lt;a href=&quot;https://social.coop/@cwebber/113517115992533495&quot;&gt;mentioned recently&lt;/a&gt; that it's possible to get parts of ActivityPub working on a static site. And hey, this site is a static site! And it's built with Haunt, which is thankfully very hackable. So I figured I'd give it a try. As of yesterday, I have some code to hackily generate &lt;a href=&quot;/social/outbox&quot;&gt;an ActivityPub outbox&lt;/a&gt;, and I &lt;em&gt;think&lt;/em&gt; I did it right... but can't quite tell yet, since I'm hosting this off of SourceHut Pages which doesn't let me set the right content-type for my AP actor objects. So testing to come later, and we'll see if I did any of this right.&lt;/p&gt;&lt;p&gt;I don't have a way to browse my blog by tag yet, but &lt;a href=&quot;/feeds/da2024.xml&quot;&gt;this RSS feed&lt;/a&gt; will include all posts with the &lt;code&gt;da2024&lt;/code&gt; tag. Maybe a tag browser will be an extra little mini-project soon.&lt;/p&gt;</content></entry></feed>