What is Pleroma?

And what the bleep do we know?

Pleroma is a distributed, object-oriented operating system/VM inspired by the original vision of Alan Kay's Smalltalk, the E programming language, and Inferno/Plan9.

The creator or creators of Pleroma are entirely unknown - it is a found operating system. The specification was discovered in a cache located in Oxyrhynchus, Egypt dating back to the 3rd century.

Pleroma is the answer to the following questions/problems:

  • What happened to Smalltalk?
  • I want to host reliable internet services from multiple computers (some on my personal network, some on the cloud, some mobile), but Kuberenetes is too complex
  • Distributed software always ends up becoming centralized due to reliability
  • Why do I need Protobufs, API specs?
  • I feel unsafe downloading/running code from Github without reading it first
  • I have a lot of computing devices and no way to manage them uniformly
  • I need concurrency without all of the baggage
  • Actor systems are pointless without a strong distributed story
  • I don't want to know, or care, about where my software is running
  • Personal computing research has been completely stagnant, despite major technology advances in the last 50 years
  • I think that E/Plan9 were ahead of their time and/or interesting but ultimately useless
  • Using Erlang doesn't make me feel good inside, and I don't know why
  • I want to host my own email, but due to a laundry list of problems, that's not possible

The four core components are:

  • Pleroma: the actual operating system
  • Hylic: a statically-typed language
  • Allo: the communication tool for interacting with Pleroma
  • Charisma: a novel IDE integrating voice and AST editing

Pleroma is versioned according to the solstices. w22 and s22 referring to the winter and summer solstices of 2022, respectively. Versions can't be patched unless there are major security issues, in which case one can append p and an incrementing number (e.g. w22p4). Otherwise, a version is frozen in time and all new development takes place on the next solstice release.

Getting Started

Docker Quickstart

This requires the latest docker + docker compose to be installed, first.

git clone https://github.com/wangell/pleroma
docker compose run pleroma

Once inside the container, run:

./pleroma start

to run the "Hello, world!" example on a single-node cluster.

You're ready to start your Pleroma journey, continue to allo instructions.

Manual Installation

Install the prerequisites: enet and libprotobuf. This can be achieved on Ubuntu by running

sudo apt-get install libenet7 libenet-dev protobuf-compiler libprotobuf-dev

Next, download by cloning the Git repository here:

git clone https://github.com/wangell/pleroma

Ensure that your locale is UTF-8. You can do this by adding the following to your .bashrc:

export LANGUAGE=en_US.UTF-8
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
export LC_TYPE=en_US.UTF-8

Continue to the installation instructions.

Getting Started - Installation

After cloning the repository and setting up your locale, follow the instructions for your specific architecture below:

x86 / x86_64

cd pleroma
./gen_make.sh
make
ln -s build/pleroma pleroma
ln -s build/allo allo

RISC V

ARM

Xen/KVM

Continue to configuration to finish setting up your cluster.

Getting Started - Configuration

Use a text editor to edit pleroma.yaml.

Assign a name and a network device for the cluster.

---
name: bruno
netdev: tun0

Configure the resources that will be made available to the cluster:

resources:
    - keyboard
    - monitor
    - mouse
    - gpu

Use tags to configure node-specific values that will be used during constraint satisfaction.

tags:
    safe: ~
    stability-rating: 4

Getting Started - Installation

Run Pleroma on the first node:

pleroma

Run Pleroma on other nodes, specifying any accessible cluster IPs:

pleroma -f 192.168.178.1

Pleroma can also automatically handle local discovery:

pleroma --auto

Allo

Allo is a cluster-management and access tool for Pleroma.

Run allo -g for the visual mode.

allo nodes will return a list of currently running nodes.

Hylic

Hylic is a statically-typed language for writing Pleroma entities/programs.

Hylic - Basics

~sys

ε HelloWorld
	δ main(env: sys►Env) -> u8
		loc a : u8 := 1 + 1

The line

~sys

imports code from another module.

ε HelloWorld

This line specifies an entity called HelloWorld. Everything in Pleroma is an entity, except those things that aren't. Every entity contains data and functions. An entity specification is not an entity itself - just a blueprint.

δ main(env: sys►Env) -> u8

Says that there is a function main, that takes a variable env of type sys.env and returns an int. The δ specifies the function as one with side-effects. Alternatively, a λ specifies the function as pure. Pure functions cannot perform side-effects.

loc a : u8 := 1 + 1

Says to create a variable a of type u8 and set its contents to 1 + 1. The type of a is a loc u8, further explained later in Messaging, Entities, Vats. By default, all variables are local. Local variables mean local to the entity.

Built-in Types

Integer: u8, u16, u32, u64

Float: f32, f64

Boolean: bool (#t/#f)

Character: char (``a`)

String: str

Conditionals

The match statement handles most conditionals in Pleroma:

? 1 == 1
    #t
        io.print("Yes!")
    #f
        io.print("No!")

The empty lines can be left off in the case of only one statement:

? 1 == 1
    #t io.print("Yes!")
    #f io.print("No!")

If no cases are given, an implied true match is inserted:

? 1 == 1
    io.print("Yes!")

Control Flow

For statements can be constructed with the pipe operator, similar to math:

my-list : [u8] = [1, 2, 3, 4]

x | my-list
    do-something(x)

Indices can be extracted from the list by adding an additional variable i, x | my-list

Return

Use to return a value from a function.

Comments

Comments can be added by surrounding a statement with . Comments can be multi-line, too.

♦ This is a comment ♦

Casting

let q: u8 = 4

let z: s8 = as(q, s8)

Hylic - Modules

Every file exists in a module, and modules are imported by path.

At the top of the file, you define your module foo. No matter what, that module can always be accessed in all other files as ~foo. If you have multiple files with foo, then you can imagine all of them are concatenated together (no ordering). Directory structure is ignored and replaced by whatever you specify.

Dependencies

TBD

Hylic - Messaging

Messaging can be performed in many different ways in Hylic and is optionally restrictive.

All objects can be called synchronously, asynchronously, and via a Promise-based interface.

Synchronous Message Passing

When we call an object synchronously, we wait for the response of the object before continuing.

anObject.doThing()

Asynchronous Message Passing

anObject ! doThing

Passes a message and ignores the result.

Promises

myProm : @u8 := anObject ! doThing

Promises can be resolved using the resolution operator:

@ promise-example
    # do something with the returned value, promise-example
    
@ let x : [@] = [more-promises1, more-promises2, more-promises3]
    # Resolve several promises

In this case, each promise variable is shadowed by the resolved version.

Vats

Vats form local object-graphs (think: thread, think: process) - they execute parallel to eachother. Every program starts in its own vat. All objects created within that vat can be accessed via a regular function call, e.g. myObject.doThis(). The function will then run synchronously. To access objects outside of one's own vat, messaging must be used, e.g. myObject ! doThis() - which executes asynchronously. Functions of objects within a vat can also be called asynchronously - but calls will not run parallely, only concurrently. When a vat gets a message, it goes into a queue. Each message is processed one by one. One can think of a vat like a thread.

Use the following syntax to construct an object in a new vat:

$anObject(1)

Constructing a new vat always returns the type @ far object-type, e.g.:

myProm : @far anObject := $anObject()

@ myProm
    # The promise is resolved to a far anObject, we can send messages now
    myProm ! doThing

Alternatively, messages can be sent directly to the promise before resolution. The messages will be preloaded in the message queue during vat formation.

myProm : @far anObject := $anObject()

myProm ! doThing

Message Processing

One of the key features of Pleroma is the ability to mix async and synchronous code. It is important to understand this feature, as it is enabled by default: all code must yield or the vat will never receive messages.

To implement an infinite loop within a vat, one must use self-messaging:

ε HelloWorld
	δ loop() -> ()
        do-something()
        ! loop

Each entity also contains a variable self, which returns a reference to the current entity self ! loop.

Pure Objects

Pure objects contain only pure functions and no data members. They cannot be instantiated and are accessed via the Entity name.

Ɵ HelloWorld
    λ add-world(hello-string: str)
        ↵ hello-string + ", world!"

And to call it from another entity, just use HelloWorld.add-world("Hello"). No need to instantiate it.

Hylic - Entity Constraints

Entities can be configured to be scheduled according to constraints. If an Entity is imported into a vat, the vat inherits the constraints. Collisions between constraints must be handled manually.

Constraints consist of a list of either tags or boolean expressions. A tag preceded by an & operator will call the equivalent function. A true boolean function/expression means that the object is able to be scheduled on a node.

~sys►resources rsc
~sys►node nd

ε HelloWorld
    - nd.is("unixborn")
    - rsc.has("keyboard")
    - rsc.gt("monitors", 2)
    - boolean-callback boolean-collision
    - nd.each()

	δ boolean-callback(env: nd►Env) -> bool
        ...
        ? rsc.present("keyboard")
            ↵ #t

Entity constraints marked with - form preambles - that is, they are executed within the kernel before object instantiation. Preamble expressions don't have access to entity members. They form an execution plan.

Entity constraints marked with + form postambles - which run after instantiation. Postamble expressions have access to entity members. Not everything can go according to plan, postambles react to and change previously made execution plans.

Hylic - Capabilities

Objects in Pleroma can only be accessed/messaged by reference. It's possible to make your objects accessible from the kernel and to guard them with ACLs.

Entities are able to access unique or node-unique system objects by adding an inoculation to the entity definition:

~sys►monad
~sys►io

ε MyEntity {pinst : @far monad►Monad, io-ctx: @far io►Io}

  δ main(env: u8) -> int
    let node-id : @u32 = pinst ! get-node-id()
    @ node-id
        ...

    io-ctx ! println("Testing, 1.2.3.")

During object instantiation, the kernel searches for node-unique and then unique objects (tagged in the preamble) until the type is matched.

Hylic - Alien Types

One of the key features of Pleroma is the ability to interface with foreign clusters.

This can be achieved using the alien reference type aln.

~wikipedia

ε HelloWorld
	δ main(env: sys.env) -> u8
        aln wiki : wikipedia.Wikipedia = wikipedia.Wikipedia()

        wiki ! get-article-id(14985782)

This creates a messageable object reference, much like a far reference - except that a proxy object is instantiated in its place. This ensures the safety + integrity of the cluster, while still allowing interfacing to foreign clusters/objects. Say goodbye to API specs and Protobufs!

Hylic - Std

Data structures

Hylic - Distributed Primitives

ARC

Entities outside of ones own vat behave similarly to heap-allocated memory - entities must be manually destroyed. For that reason, a distributed automatic reference count object can be used to handle destruction of entities (a vat will automatically be destroyed when no more entities remain).

~dis

...

dis.Arc.create($myObj)

Transactional / Checkpoint

CRDTs

Key-value Store

Consistent Hashing

Zeno Filesystem

Zeno is the distributed filesystem that powers Pleroma. It is where all (most) system and user files are stored.

Two entity types power Zeno: ZenoMaster and ZenoNode.

Each node with a storage resource runs a ZenoNode entity, and a cluster-wide ZenoMaster entity runs on any regular node.

All files are stored as chunks spread across ZenoNodes, which are indexed by the ZenoMaster node. The chunk size is tunable, but is set to a sane default.

Each chunk is replicated across 3 nodes (or however many stable nodes your system has).

Telescoping Tag System

The primary difference in usability between a typical filesystem and Zeno, is that Zeno uses tags rather than paths.

On a typical filesystem, you might have a path called System > Programs > ... which lists all directories of every program.

In Zeno, files have a name, but directories don't exist. Instead, every file has multiple tags, e.g.

filename: MyPhoto.jpg
user: MyUser
who: Will
type: FamilyPhotos

and you can view these tags through a "telescope" or "lens". The lens allows you to change the structure of your files without affecting the underlying files.

Here's one example:

Let's say you wanted to see all your personal photos that contain you, and all your personal photos that contain Bob. You could create two directories, one called "Me" and another called "Bob", and copy and paste the photos into each directory. There would be some duplication, as you and Bob are coworkers and appear in several photos together.

Alternatively, we could just add a tag in-photo: Bob or in-photo: Will to each photo (or you add both tags). Now we can easily use a lens to achieve what we wanted:

Photos > Bob:(in-photo:Bob), Me:(in-photo:Me), Together:(in-photo:Bob + in-photo:Me)

When viewed through this lens, you'll see a directory Photos that contains three other directories Me, Bob, and Together. All photos tagged with in-photo: Bob will be placed in the Bob directory, etc. No file duplication required.

By default, Pleroma provides a system lens, and a user lens that mimics what one might expect from a classic operating system.

Usage

~zeno

checkout(path: str, lens: Lens) -> Zfile - checks out and locks the current file. It calls ZenoMaster, finds the location of the associated file chunks, assembles them by contacting each ZenoNode entity, and then returns the file. You can specify what type of lock-access is required: multiple-reader, multiple-reader-single-writer, or exclusive.

look(lens: Lens) - Takes a lens pattern and returns the associated file view.

Capabilities

Programs are namespaced to their own tag (with the right capabilities). Programs naturally cannot see anything outside of their own tag, unless they specifically request it via another capability.

Local Filesystem

Local filesystems can still be accessed as normal using ~fs, but it is discouraged unless absolutely necessary.

Pleromic Software

Erd-talk

Erd-talk is a web-of-trust/reputation system for URIs.

  1. URIs can be assigned key/values, e.g. "example.com:trust:1.0"
  2. You can query friendly erd-talk clusters for their values and compute against them, e.g. "Gather all the trust values from friends about URI x and return the average"
  3. You can handle queries in a custom way. Instead of just returning a value, you can compute, or recursively (via friendly clusters) compute, a new value.

What can erd-talk be used for?

  • Reweight Amazon/Yelp reviews according to your friends/family.
  • Discover new or interesting websites
  • Help decide whether a program/website/business is trustworthy or not
  • Calculate impact scores of papers independent of publishers
  • Find inaccuracies in news stories

hippie science

Voice control software that interfaces with all parts of Pleroma.

kokomoplux

Inter-cluster chat program.

victory

Everywhen

These serve as the inspiration and basis for Pleroma