Librarian of Alexandria


This is an entry from a list of projects I hope to some day finish. Return to the backburnered projects index.

What is it? A reverse proxy and web server interface.

Aloysius was originally inspired by the old academic servers where a person could host static pages under their username. What I wondered was, how could I design a system like that for dynamic sites, where a given user account could run their own services via a reverse proxy but not conflict with any other service on the machine?

So the approach was based on directories and symlinks. The top-level configuration is a directory that contains zero or more subdirectories, each of which uses different files to describe both filters and forwarding logic. Filters are based on matching on either path (i.e. the HTTP requst path) or domain (i.e. the server) or both, and then they can forward in one of three ways—maybe more over time, too, but these were the first three I experimented with—as specified by a mode file: either http (which forwards the request to host host on port port), or redir (which uses HTTP response code resp and redirects to host host), or aloysius, where you specify another directory by the symlink conf, and then recursively check configuration there.

That last one is important, because it allows you to symlink to any readable directory. One idea here is that different subdomains can map to different users on the same machine. For example, let's say you're keeping your Aloysius configuration in $ALOYSIUSDIR, and you've got a user on the machine yan who doesn't have root access but wants to be able to run some dynamic sites. You can set up something like the following:

# create a dir to forward yan's config
$ mkdir -p $ALOYSIUSDIR/00-yan
# match against
$ echo >$ALOYSIUSDIR/00-yan/domain
# use a recursive aloysius config
$ echo aloysius >$ALOYSIUSDIR/00-yan/mode
# and forward it to yan's home directory
$ ln -s /home/yan/aloysius $ALOYSIUSDIR/00-yan/conf

Now yan can write their own configuration for whatever dynamic web services they want—at least, if supported by the ability for them to run user-level services—and it doesn't require giving them root access to change the other configuration for the reverse proxy. It falls directly out of Unix symlinks and permissions!

Why write it? Honestly, it's not terribly useful anymore: I run my own servers using mostly nginx and am pretty comfortable with configuration thereof. But I still think it's a cool idea, and I clearly think this directory-and-symlink-based system of configuration has legs (which I know I've written about before).

I still think it'd be convenient! After all, I have for a long time used a directory of files (along with a wildcard) to configure Nginx: this simply makes that the first-class way of doing configuration! But also, there's a lot of work that goes into writing a good web server, and this is also solving a problem which the tech world seems to no longer have: namely, how to shunt around requests to different web services all living on the same machine, since instead we abstract those away into their own containers and treat them like separate machines.

Why the name? The name Aloysius was chosen pretty much at random. I originally called it Melvil after Melvil Dewey due to a loose metaphor between the tiny scraps of information used in configuration and the card catalogues that Melvil Dewey had helped popularize, but only because at the time I didn't realize what an absolutely awful human being Melvil Dewey was.

#backburner #software #tool #web

This is an entry from a list of projects I hope to some day finish. Return to the backburnered projects index.

What is it? A protocol for writing RSS readers, heavily inspired by the maildir format.

The core idea of Lektor is that you separate out the typical operation of an RSS reader into two programs which can work concurrently over a specific directory. One program is called a fetcher, and its purpose is to read remote feeds (of any format) and write them into the lektordir format. The other program is called a viewer, and its purpose is to take the files inside the lektordir and view them.

I've got the full format specified elsewhere but the core idea is that you've got a specified directory called your lektordir, which in turn contains four directories: src, tmp, new, and cur. The src directory contains representations of feeds, new contains unread posts, and cur contains read posts. (The tmp directory is an explicit staging place: fetchers will create posts in there and then atomically move them to new, which means that it's impossible for a viewer to ever observe an incomplete post. Even if a fetcher crashes, it will only be able to leave garbage in tmp, and not in new.)

The representations of feeds and posts are themselves also directories, with files and file contents standing in for a key-value structure. That means a given feed is represented by a directory which contains at least an id (its URI) a name (its human-readable name), and a post is a directory which contains at least an id (its URI), a title (its human-readable name), a content file (which is its content represented as HTML), and a feed (a symlink to the feed that produced it.) In both cases, there are other optional files, and also ways of representing fetcher- or viewer-specific metadata.

The use of directories means that fetchers and viewers don't even need a specific data serialization or deserialization format: unlike maildir, you don't even need to “parse” the format of individual entries. This means fetchers need only a bare minimum of functionality in order to write feeds—I had test programs which used basic shell scripts that wouldn't have been able to safely serialize JSON, but were able to write this format trivially.

My intention is not just to design the format—which has already been pretty thoroughly specified, albeit in beta format and without having been subjected to much testing—but also to write the tools needed to write an RSS and maybe ActivityPub reader on top of this format.

Why write it? Well, I wanted to write an RSS reader, because none of the existing ones are exactly what I want, and this seemed like a good way of splitting up the implementation into bite-sized chunks.

There are some interesting features you get out of separating out the fetcher and viewer operation. For one, fetchers become ridiculously simple: they don't even need to be full services, even, they can just be cron jobs which hit an endpoint, grab some data, and toss it into the lektordir. Instead of a reader which needs to support multiple simultaneous versions (e.g. Atom, various versions of RSS, newer ActivityStreams-based formats) you can have a separate fetcher for each. For that matter, you can have fetchers which don't strictly correspond to a real “feed”: for example, you can have one which uses a weather API to include “posts” that are just sporadic weather updates, or one which chunks pieces of your system log and puts one in your feed every day to give you status updates, or whatnot.

Separating out viewers means that you can now have multiple views on the same data as well. Maybe one viewer only cares about webcomics, so you can pull it up and read webcomic pages but let it omit blog posts. Another gives you a digest of headlines and emails them to you, leaving them unread (but of course omits anything you happen to have read during the day.) Another does nothing but pop up a message if you let unread posts pile up too long. Hell, you could have one that takes your combined posts and… puts them together into an RSS feed. Why not?

The cool thing about lektordir to me is that it lowers the barrier to entry to writing RSS-reader-like applications. It takes the parts and splits them behind a well-defined interface. But there's a lot of cool stuff that you get from trying to apply a one-thing-well philosophy to problems like this!

Why the name? It's for reading, and lektor means “reader” in several languages. I'll probably change the name at some point, though, because there's also a plain-text CMS called Lektor.

#backburner #software #web #tool

This is an entry from a list of projects I hope to some day finish. Return to the backburnered projects index.

What is it? The digital tabletop I want for the tabletop games I play, scripted the way I want to script it. My original notes on the topic called this system Beholder, but more recently I've taken to calling it Parley.

The information display part is maybe a little bit less interesting, but fundamentally this is because of how I play tabletop games: what I want is first and foremost something wiki-like, and secondarily a mapping system. Many of the games I play don't care about fine-grained maps, but instead are concerned with characters and factions and whatnot: I want to bring that information front-and-center. I want a digital tabletop to function as a shared dossier, including the ability for players to add their own pieces of information (e.g. descriptions of NPCs or items) that are stored tagged with the player/character who wrote them. Maps should still be present, but they're not always the star of the show.

The scripting component is the more interesting part: I want the ability to express game rules using a total, decidable language with reusable components. I know that “this should use a decidable language” is a thing I've talked about before in these project posts—both Virgil and Van de Graaff include such a language, and won't be the last ones—but I really am interested in systems that do decidable computation in a domain-specific way. The intention here is that I can support a new tabletop game or system by chaining together an appropriately-designed set of computational primitives.

Say I want to describe the rules of the game Apocalypse World. In particular, let's look at the Apocalypse World rule for Do Something Under Fire, which is described like this:

When you do something under fire, or dig in to endure fire, roll+cool. On a 10+, you do it. On a 7–9, you flinch, hesitate, or stall: the MC can offer you a worse outcome, a hard bargain, or an ugly choice. On a miss, be prepared for the worst.

I might begin (using handwavey, ML-inspired syntax here—I hadn't made any syntax choices before) by writing a macro which can be used to describe the way all dice rolls in Apocalypse World have three fixed outcomes:

let aw_roll(modifier:, success:, partial:, failure:) =
  match 2d6 + modifier {
    | n when n >= 10 => success
    | n when n <= 6  => failure
    | otherwise      => partial

I can then express a specific move by instantiating this macro, using constructs to express that the modifier will be filled in with the character's relevant stat and the various outcomes will result in specific pieces of informative text.

let do_something_under_fire = aw_roll
  ( modifier:,
      say("You do it."),
      say("You flinch, hesitate, or stall: the MC can offer
           you a worse outcome, hard bargain, or ugly choice."),
      say("Be prepared for the worst."),

Finally, this and other macros will be assembled into a single set of rules which can be exposed to the player in a clean way, which gives the player a palette of the abilities at their disposal. Importantly, despite looking like an imperative program, the above code would actually be purely declarative: the result of do_something_under_fire is an abstract tree of possibilities, so the Parley system would be able to understand not just how to “run” it with a particular random roll, but also how to, for example, express that rule in prose, since it understands expressions like 2d6 + modifier symbolically and abstractly.

Why write it? Well, for one, basically every digital tabletop service I've used (like Roll20) assumes that you're playing Dungeons and Dragons or at least a game significantly like it. That means they build first and foremost around maps, and usually grid-based maps of the kind usable for D&D combat.

While I'm not against D&D, it's also not my favorite kind of tabletop game. As I mentioned before, I mostly prefer games that aren't focused on tactical grid-based combat. My usual games of Blades in the Dark or Apocalypse World are far more about the history and interactions of NPCs and factions, and maps tend to be sketchy and collaborative instead of the rigorous structuring principle of the whole game. I've mostly been using Roll20 for my games, but a bunch of the UI around shared notes feels like an afterthought, and I think designing for shared notes up-front (maybe borrowing some ideas from Notion, a tool which I love) would yield some great benefits.

I also am fascinated by the language design necessary to describe tabletop games, and I think that's the most appealing part of this project to me. You can certainly express the rules for a new tabletop game using Roll20's system… but that system is just, “Program it with JavaScript.” I don't think that's a bad design, to be clear! But I think there's a lot you can do if you sat down and designed a language specifically for the task of describing and implementing tabletop game rules, especially from the point of view of being able to statically analyze the game structure.

For example, I can imagine using the same underlying “programming language” to build out not just a web interface for Parley but also a kind of rigorous rule-book: the same declarative description could just as easily be analyzed and serialized as it could be run. That sort of interface could even allow for a game designer to start doing an abstract analysis of the patterns and possibilities inherent in the rules. There's also some really fascinating ideas to be borrowed from Chris Martens' linear-logic-based Ceptre language which I haven't even scratched the surface of.

Why the name? The original name, Beholder, was because it was fundamentally an information-display application, but also referenced the famous D&D monster of the same name. The newer name, Parley, is because it's also a chat-like application featuring a feed of dice rolls and results, and because Parley is a Dungeon World move used to talk to people.

#backburner #software #web