forumdevfromhell/Errorum

Another Forum software from hell

0

stars

1

commits

C

primary language

Sep 13, 2026

updated

README

ERRORUM 0.3

SchrödingerDB + Browser Observer

ERRORUM is a browser-usable forum whose storage engine is based on a rule that should have remained a joke:

A record is not stored. A record is inferred from several mutually dependent filesystem facts that are individually insufficient.

Version 0.3 adds the missing public-facing piece: a normal web browser can now register, log in, browse categories, create threads, reply, and like posts over HTTP. The browser-facing page looks almost ordinary. The machinery underneath remains ERRORUM.

This is experimental art-project software. Do not store anything important in it.


Fastest possible setup

Requirements:

  • Linux
  • Python 3
  • GCC
  • GNU Make
  • a filesystem supporting user.* extended attributes
  • ping only if you want the ICMP notification demo

On Debian/Ubuntu-like systems the useful packages are normally:

sudo apt install python3 gcc make attr curl

Unzip and start it:

unzip ERRORUM-v0.3-BrowserHell.zip
cd Errorum
./run.sh serve 0.0.0.0 8666

Then open:

http://SERVER-IP:8666/

The web UI lets users register themselves. You do not need to pre-create accounts with the CLI anymore.

For local-only testing use:

./run.sh serve 127.0.0.1 8666

The data tree defaults to ./hell. To put data somewhere else:

ERRORUM_ROOT=/var/lib/errorum/hell ./run.sh serve 127.0.0.1 8666

You can also use environment variables:

ERRORUM_BIND=127.0.0.1 ERRORUM_PORT=8666 ERRORUM_ROOT=/var/lib/errorum/hell ./run.sh serve

Recommended server setup

The package includes install-systemd.sh for a dedicated service account.

From the extracted Errorum directory:

sudo ./install-systemd.sh

Defaults:

application: /opt/errorum
state:       /var/lib/errorum/hell
bind:        127.0.0.1
port:        8666
service:     errorum.service

You can override the bind address and port:

sudo ./install-systemd.sh /opt/errorum 0.0.0.0 8666

Then:

systemctl status errorum
journalctl -u errorum -f

Binding to 127.0.0.1 is recommended when Tor or a reverse proxy is in front of ERRORUM. Binding directly to 0.0.0.0 exposes the raw HTTP service to your network.

ERRORUM does not provide TLS. If you expose it directly to the public Internet, put it behind HTTPS. Otherwise login passwords travel inside ordinary HTTP.


Tor onion setup

ERRORUM works particularly well as a Tor onion service because the forum can remain bound only to loopback.

Run ERRORUM on:

127.0.0.1:8666

Then add a hidden service to your Tor configuration, commonly /etc/tor/torrc:

HiddenServiceDir /var/lib/tor/errorum/
HiddenServicePort 80 127.0.0.1:8666

Restart or reload Tor using the service name used by your distribution, then read:

sudo cat /var/lib/tor/errorum/hostname

Open that .onion address in Tor Browser. Users can register and use ERRORUM entirely through the browser.

Do not point the hidden-service directory at the ERRORUM data directory. Even this project has limits.


Browser features in 0.3

The browser observer supports:

  • public category/thread reading
  • browser registration
  • browser login/logout
  • creating categories implicitly by creating a thread in a new category
  • creating threads
  • replies
  • likes
  • current-admin display
  • current schema display
  • current DNS index salt display
  • an About page explaining why everything is wrong

HTTP responses also leak ERRORUM state through deliberately weird headers:

X-eRrOrUm-sChEmA
X-eRrOrUm-aDmIn
X-eRrOrUm-oMeN

X-eRrOrUm-oMeN encodes the HTTP status using capitalization because HTTP header case-insensitivity looked like unused storage capacity.


What happens when a browser logs in

A browser submits a username and password.

The password is hashed. ERRORUM reconstructs the expected hash using filesystem identity. It then generates a temporary Makefile. GNU Make is asked whether the authentication target can be built.

If Make succeeds, ERRORUM forks a process whose lifetime is the login session.

The browser receives a signed cookie containing the username and the process PID. A session is valid only while:

  1. the cookie signature still matches the current filesystem-derived key,
  2. the user's session xattr still references that PID,
  3. /proc/PID still exists.

When the session process ceases to exist, the session ceases to exist.

Changing the mounted-filesystem count changes the schema representation, which also changes the filesystem-derived authentication/session key. Yes, mounting something can log people out or make old authentication material stop resolving. This is intentional architecture, not recommended architecture.


What happens when a browser creates a post

The HTTP handler does not insert a database row because there is no database row.

It does roughly this:

browser POST
    ↓
logged-in PID session observed through /proc
    ↓
mkdir race creates transaction object
    ↓
kernel gives process PID + ephemeral TCP port + inode
    ↓
those become the post ID
    ↓
post directory mode bits are selected
    ↓
.clock directory mtime becomes data
    ↓
title/body split into mutually dependent representations
    ↓
representation A goes into xattrs
representation B goes into symlink targets
    ↓
filesystem metadata is required to combine them again

There is no regular file containing the canonical title or body.


SchrödingerDB

A title/body is reconstructed from:

  • extended-attribute representation A
  • symlink representation B
  • post inode
  • Unix permission bits
  • .clock directory mtime
  • a stored universe nonce used to check that observation is still coherent

Neither representation A nor representation B contains the post body by itself.

Changing an inode, chmodding a post directory, touching its clock, losing an xattr, or replacing one of its reality symlinks can change the observed value or collapse it into a NULL-like result.

A traditional database has a row.

SchrödingerDB has an argument between filesystem facts.


The executable is compiler failure

The actual forum runtime is not distributed as ordinary Python source.

errors.c is intentionally invalid C containing thousands of GCC #error diagnostics. Those diagnostics encode the runtime bytes.

boot.py does this every time ERRORUM starts:

gcc -E errors.c
    ↓
compilation MUST fail
    ↓
parse ERRORUM_* diagnostics from stderr
    ↓
reconstruct runtime bytes
    ↓
verify exact byte count + SHA-256
    ↓
create Linux memfd
    ↓
execute /proc/self/fd/N

If errors.c compiles successfully, ERRORUM refuses to start because somebody fixed the source code.

config.c is intentionally invalid too. Configuration existing as successful C would be culturally inappropriate.


Users do not have stored usernames

A username is not the directory name.

On registration ERRORUM creates a directory, receives its inode from the filesystem, XORs the username bytes with the low inode byte, Base32-encodes the result, and renames the directory.

To discover the username later it needs both:

encoded directory name + inode

A restore that changes inode identity can therefore make a perfectly preserved user directory decode as somebody else or as inode drift.


Admin is whoever is closest to Unix

There is no admin column, flag, group, ACL, or configuration entry.

ERRORUM takes the low 16 bits of the current Unix timestamp and places them on a 16-bit ring. Each username+inode hash is placed on the same ring.

The nearest user is admin right now.

./run.sh whoisadmin

As Unix time advances, the nearest user can change without anyone modifying the forum.

The web UI displays the currently observed admin on every page.

This is authorization by clock geometry.


Passwords and GNU Make

Passwords are SHA-256 hashed, then the digest is XORed with a key derived from:

  • ERRORUM root filesystem device
  • ERRORUM root inode
  • current schema representation

Authentication generates a temporary Makefile dependency graph. Login succeeds only when Make can build the requested authentication target.

CLI example:

./run.sh auth alice swordfish

A sufficiently different restore or a schema change can make the same password stop authenticating.


Post IDs are environmental accidents

There is no AUTO_INCREMENT.

A new ID combines:

  • creating process PID
  • kernel-assigned ephemeral TCP source port
  • low bits of the new post directory inode

Example:

18a6-a35d-17d

Other software running on the machine can indirectly influence future IDs by consuming PIDs and ports.


DNS is the database index

Thread ordering is salted using the resolver's current view of localhost.

Change resolver behavior and the order can change without changing the threads.

The browser shows the current DNS index salt in its header.


Mounted filesystems are the schema version

There is no schema-version file.

./run.sh schema

returns something like:

mounts-31

Mount another filesystem and congratulations, you performed a migration.

This also participates in authentication/session key derivation, because merely calling it a schema version was insufficiently dangerous.


Randomness is free disk space

./run.sh random 100

returns free bytes modulo 100.

Tor writes state? phpBB grows? FTForum stores something? A package gets installed? Fate may change.

This ambient state does not normally rewrite existing post content, but it does influence operations that deliberately consume ERRORUM's disk-pressure oracle.


Likes are symlinks

A like is a symlink from a post's likes/ directory to the encoded user directory.

There is no likes table.

Deleting or damaging the target user leaves a dangling relationship. Dangling relationships are treated as NULL-like structural failures rather than being given the dignity of a foreign-key exception.


Replies

Replies are normal SchrödingerDB post objects with an xattr containing a parent reference.

Their title is reconstructed like every other title and normally begins with RE:. Their bodies still require the filesystem metadata and both mutually dependent representations.


Transactions

Post creation uses a mkdir() race as a lock/transaction primitive.

EEXIST means the transaction lost the mutex race and retries.

There is also a deliberately philosophical demo:

./run.sh forktxn commit
./run.sh forktxn rollback

The transaction is expressed through process lifetime.


Notifications are ICMP

CLI only for now:

./run.sh notify alice "you have mail"

The message is hashed into a ping payload and emitted as an ICMP loopback packet.

There is no message queue because localhost was sitting there doing nothing.


NULL

A broken/missing structural representation is a NULL-like observation failure.

Examples include:

  • missing reality symlink
  • dangling relationship
  • missing xattr
  • changed metadata that prevents coherent reconstruction

You may see values such as:

[NULL: FileNotFoundError]

That is not a friendly error message. It is a database value having a bad day.


CLI quick reference

Initialize:

./run.sh init

Register/login:

./run.sh register alice swordfish
./run.sh auth alice swordfish

Create/list/show:

./run.sh post General alice "Hello" "There is no canonical copy of this sentence."
./run.sh list General
./run.sh show General POST_ID

Reply/like:

./run.sh reply General POST_ID alice "reply body"
./run.sh like General POST_ID bob

Inspect the crime scene:

./run.sh inspect General POST_ID
./run.sh whoisadmin
./run.sh schema
./run.sh random 100
./run.sh online

Start browser mode:

./run.sh serve 0.0.0.0 8666

Roadmap: how we got here

v0.1 - Compiler Error Forum

The first ERRORUM proved the core executable-format joke.

The runtime was encoded inside intentional GCC #error diagnostics. boot.py compiled something that was required to fail, parsed stderr, reconstructed the program, placed it into a Linux memfd, and executed it without shipping a normal runtime source file.

Storage was already wrong:

  • posts were directories
  • author identity depended on inode information
  • titles used symlink targets
  • bodies lived in xattrs
  • likes were symlinks
  • normal tree output could not reveal the real body

It was cursed, but most fields still had one obvious weird representation.

v0.2 - SchrödingerDB

v0.2 removed that remaining dignity.

Post fields became observations reconstructed from multiple mutually dependent representations plus filesystem metadata. It added the wider ambient-system architecture:

  • usernames require encoded directory name + inode
  • admin determined by distance to Unix time
  • GNU Make authentication
  • filesystem-derived authentication key
  • PID + ephemeral port + inode post IDs
  • DNS-dependent ordering
  • mkdir transaction locking
  • sessions represented by PIDs and /proc
  • ICMP notifications
  • free-disk-space randomness
  • mounted-filesystem schema version
  • permission bits as data
  • timestamps as data
  • header-capitalization API encoding
  • HTTP status codes treated as application values
  • kernel page cache accepted as the cache layer
  • dangling relationships interpreted as NULL-like state

v0.2 was usable from the command line but had no public browser frontend.

v0.3 - Browser Hell

v0.3 makes the forum actually handable to other humans.

It adds a browser observer while deliberately keeping the backend architecture intact:

  • public HTTP server
  • self-service web registration
  • web login through GNU Make authentication
  • cookie sessions whose actual existence is a living PID
  • category/thread pages
  • web thread creation
  • web replies
  • web likes
  • current rotating admin shown live
  • schema and DNS state shown live
  • weird state encoded into HTTP headers
  • systemd installer
  • Tor hidden-service deployment instructions

The important design rule is that the web layer does not normalize the storage engine. It merely observes it.

Future crimes

Possible later versions could make unrelated filesystem activity participate directly in post observation, add SSE/live updates backed by FIFOs or signals, encode moderation into ACLs, make search a find + getfattr query planner, and expose a deliberately cursed API whose request parameters are encoded primarily in header capitalization.

At that point a log rotation elsewhere on the machine could potentially alter what the forum observes. v0.3 intentionally stops just short of that because users still need to be able to read the thing.


Backups

A normal copy can destroy ERRORUM semantics even when every visible byte looks present.

You need to preserve as much filesystem identity/metadata as possible:

  • xattrs
  • permissions
  • symlinks
  • timestamps
  • ownership where relevant

Even then, inode identity and filesystem-derived keys may differ after restore.

A Linux tar attempt should at minimum use options such as:

sudo tar --xattrs --acls --numeric-owner -cpf errorum-backup.tar /var/lib/errorum

That is still not a promise that authentication or inode-derived identities will survive restoration onto another filesystem.

For maximum fidelity, filesystem/block-level snapshots are more appropriate than a logical file copy.

This is a warning, not a backup guarantee.


Manual inspection

Try:

tree hell
getfattr -d -m - hell/categories/General/posts/*
ls -lai hell/users

tree cannot show the canonical post body because there isn't one.

cat continues to be remarkably unhelpful.


Development / rebuilding the failed executable

forge.py is included as a development tool. The distributable intentionally does not include program.py.

During development:

./forge.py program.py errors.c

Then update EXPECTED_BYTES and EXPECTED_SHA256 inside boot.py, remove program.py, and test the package again.

If you accidentally distribute the reconstructed runtime as normal source, the forum will still run, but spiritually you have failed.


Smoke test

Run:

./smoke-test.sh

It verifies core CLI operations. If curl is installed, v0.3 also boots the browser frontend on loopback, registers a web user, creates a thread over HTTP, follows the resulting thread URL, and verifies the observed body.

Expected final line:

ERRORUM_0_3_SMOKE_OK

Security reality

ERRORUM is intentionally bizarre, but the browser interface still tries not to make the obvious web mistakes part of the joke:

  • output is HTML-escaped
  • browser cookies are HttpOnly and SameSite=Lax
  • session cookies are signed
  • request bodies are size-limited
  • usernames/categories are restricted character sets

However, this is still experimental software with an intentionally hostile storage model.

There is no built-in TLS, no claim of production security, no promise that data survives restore, and no reason to place sensitive information in it.

Use HTTPS through a reverse proxy for normal public Internet deployment, or keep it loopback-only behind a Tor onion service.


Design statement

A conventional forum asks:

What does the database row say?

ERRORUM asks:

Given the inode, permission bits, timestamps, xattrs, symlink targets, mount state, resolver state, process table, free disk space and current Unix time, what does reality appear to believe this post is right now?

The browser is only an observer.

That is SchrödingerDB.

Contributors

forumdevfromhell/Errorum

Another Forum software from hell

0

stars

1

commits

C

primary language

Sep 13, 2026

updated

README

ERRORUM 0.3

SchrödingerDB + Browser Observer

ERRORUM is a browser-usable forum whose storage engine is based on a rule that should have remained a joke:

A record is not stored. A record is inferred from several mutually dependent filesystem facts that are individually insufficient.

Version 0.3 adds the missing public-facing piece: a normal web browser can now register, log in, browse categories, create threads, reply, and like posts over HTTP. The browser-facing page looks almost ordinary. The machinery underneath remains ERRORUM.

This is experimental art-project software. Do not store anything important in it.


Fastest possible setup

Requirements:

  • Linux
  • Python 3
  • GCC
  • GNU Make
  • a filesystem supporting user.* extended attributes
  • ping only if you want the ICMP notification demo

On Debian/Ubuntu-like systems the useful packages are normally:

sudo apt install python3 gcc make attr curl

Unzip and start it:

unzip ERRORUM-v0.3-BrowserHell.zip
cd Errorum
./run.sh serve 0.0.0.0 8666

Then open:

http://SERVER-IP:8666/

The web UI lets users register themselves. You do not need to pre-create accounts with the CLI anymore.

For local-only testing use:

./run.sh serve 127.0.0.1 8666

The data tree defaults to ./hell. To put data somewhere else:

ERRORUM_ROOT=/var/lib/errorum/hell ./run.sh serve 127.0.0.1 8666

You can also use environment variables:

ERRORUM_BIND=127.0.0.1 ERRORUM_PORT=8666 ERRORUM_ROOT=/var/lib/errorum/hell ./run.sh serve

Recommended server setup

The package includes install-systemd.sh for a dedicated service account.

From the extracted Errorum directory:

sudo ./install-systemd.sh

Defaults:

application: /opt/errorum
state:       /var/lib/errorum/hell
bind:        127.0.0.1
port:        8666
service:     errorum.service

You can override the bind address and port:

sudo ./install-systemd.sh /opt/errorum 0.0.0.0 8666

Then:

systemctl status errorum
journalctl -u errorum -f

Binding to 127.0.0.1 is recommended when Tor or a reverse proxy is in front of ERRORUM. Binding directly to 0.0.0.0 exposes the raw HTTP service to your network.

ERRORUM does not provide TLS. If you expose it directly to the public Internet, put it behind HTTPS. Otherwise login passwords travel inside ordinary HTTP.


Tor onion setup

ERRORUM works particularly well as a Tor onion service because the forum can remain bound only to loopback.

Run ERRORUM on:

127.0.0.1:8666

Then add a hidden service to your Tor configuration, commonly /etc/tor/torrc:

HiddenServiceDir /var/lib/tor/errorum/
HiddenServicePort 80 127.0.0.1:8666

Restart or reload Tor using the service name used by your distribution, then read:

sudo cat /var/lib/tor/errorum/hostname

Open that .onion address in Tor Browser. Users can register and use ERRORUM entirely through the browser.

Do not point the hidden-service directory at the ERRORUM data directory. Even this project has limits.


Browser features in 0.3

The browser observer supports:

  • public category/thread reading
  • browser registration
  • browser login/logout
  • creating categories implicitly by creating a thread in a new category
  • creating threads
  • replies
  • likes
  • current-admin display
  • current schema display
  • current DNS index salt display
  • an About page explaining why everything is wrong

HTTP responses also leak ERRORUM state through deliberately weird headers:

X-eRrOrUm-sChEmA
X-eRrOrUm-aDmIn
X-eRrOrUm-oMeN

X-eRrOrUm-oMeN encodes the HTTP status using capitalization because HTTP header case-insensitivity looked like unused storage capacity.


What happens when a browser logs in

A browser submits a username and password.

The password is hashed. ERRORUM reconstructs the expected hash using filesystem identity. It then generates a temporary Makefile. GNU Make is asked whether the authentication target can be built.

If Make succeeds, ERRORUM forks a process whose lifetime is the login session.

The browser receives a signed cookie containing the username and the process PID. A session is valid only while:

  1. the cookie signature still matches the current filesystem-derived key,
  2. the user's session xattr still references that PID,
  3. /proc/PID still exists.

When the session process ceases to exist, the session ceases to exist.

Changing the mounted-filesystem count changes the schema representation, which also changes the filesystem-derived authentication/session key. Yes, mounting something can log people out or make old authentication material stop resolving. This is intentional architecture, not recommended architecture.


What happens when a browser creates a post

The HTTP handler does not insert a database row because there is no database row.

It does roughly this:

browser POST
    ↓
logged-in PID session observed through /proc
    ↓
mkdir race creates transaction object
    ↓
kernel gives process PID + ephemeral TCP port + inode
    ↓
those become the post ID
    ↓
post directory mode bits are selected
    ↓
.clock directory mtime becomes data
    ↓
title/body split into mutually dependent representations
    ↓
representation A goes into xattrs
representation B goes into symlink targets
    ↓
filesystem metadata is required to combine them again

There is no regular file containing the canonical title or body.


SchrödingerDB

A title/body is reconstructed from:

  • extended-attribute representation A
  • symlink representation B
  • post inode
  • Unix permission bits
  • .clock directory mtime
  • a stored universe nonce used to check that observation is still coherent

Neither representation A nor representation B contains the post body by itself.

Changing an inode, chmodding a post directory, touching its clock, losing an xattr, or replacing one of its reality symlinks can change the observed value or collapse it into a NULL-like result.

A traditional database has a row.

SchrödingerDB has an argument between filesystem facts.


The executable is compiler failure

The actual forum runtime is not distributed as ordinary Python source.

errors.c is intentionally invalid C containing thousands of GCC #error diagnostics. Those diagnostics encode the runtime bytes.

boot.py does this every time ERRORUM starts:

gcc -E errors.c
    ↓
compilation MUST fail
    ↓
parse ERRORUM_* diagnostics from stderr
    ↓
reconstruct runtime bytes
    ↓
verify exact byte count + SHA-256
    ↓
create Linux memfd
    ↓
execute /proc/self/fd/N

If errors.c compiles successfully, ERRORUM refuses to start because somebody fixed the source code.

config.c is intentionally invalid too. Configuration existing as successful C would be culturally inappropriate.


Users do not have stored usernames

A username is not the directory name.

On registration ERRORUM creates a directory, receives its inode from the filesystem, XORs the username bytes with the low inode byte, Base32-encodes the result, and renames the directory.

To discover the username later it needs both:

encoded directory name + inode

A restore that changes inode identity can therefore make a perfectly preserved user directory decode as somebody else or as inode drift.


Admin is whoever is closest to Unix

There is no admin column, flag, group, ACL, or configuration entry.

ERRORUM takes the low 16 bits of the current Unix timestamp and places them on a 16-bit ring. Each username+inode hash is placed on the same ring.

The nearest user is admin right now.

./run.sh whoisadmin

As Unix time advances, the nearest user can change without anyone modifying the forum.

The web UI displays the currently observed admin on every page.

This is authorization by clock geometry.


Passwords and GNU Make

Passwords are SHA-256 hashed, then the digest is XORed with a key derived from:

  • ERRORUM root filesystem device
  • ERRORUM root inode
  • current schema representation

Authentication generates a temporary Makefile dependency graph. Login succeeds only when Make can build the requested authentication target.

CLI example:

./run.sh auth alice swordfish

A sufficiently different restore or a schema change can make the same password stop authenticating.


Post IDs are environmental accidents

There is no AUTO_INCREMENT.

A new ID combines:

  • creating process PID
  • kernel-assigned ephemeral TCP source port
  • low bits of the new post directory inode

Example:

18a6-a35d-17d

Other software running on the machine can indirectly influence future IDs by consuming PIDs and ports.


DNS is the database index

Thread ordering is salted using the resolver's current view of localhost.

Change resolver behavior and the order can change without changing the threads.

The browser shows the current DNS index salt in its header.


Mounted filesystems are the schema version

There is no schema-version file.

./run.sh schema

returns something like:

mounts-31

Mount another filesystem and congratulations, you performed a migration.

This also participates in authentication/session key derivation, because merely calling it a schema version was insufficiently dangerous.


Randomness is free disk space

./run.sh random 100

returns free bytes modulo 100.

Tor writes state? phpBB grows? FTForum stores something? A package gets installed? Fate may change.

This ambient state does not normally rewrite existing post content, but it does influence operations that deliberately consume ERRORUM's disk-pressure oracle.


Likes are symlinks

A like is a symlink from a post's likes/ directory to the encoded user directory.

There is no likes table.

Deleting or damaging the target user leaves a dangling relationship. Dangling relationships are treated as NULL-like structural failures rather than being given the dignity of a foreign-key exception.


Replies

Replies are normal SchrödingerDB post objects with an xattr containing a parent reference.

Their title is reconstructed like every other title and normally begins with RE:. Their bodies still require the filesystem metadata and both mutually dependent representations.


Transactions

Post creation uses a mkdir() race as a lock/transaction primitive.

EEXIST means the transaction lost the mutex race and retries.

There is also a deliberately philosophical demo:

./run.sh forktxn commit
./run.sh forktxn rollback

The transaction is expressed through process lifetime.


Notifications are ICMP

CLI only for now:

./run.sh notify alice "you have mail"

The message is hashed into a ping payload and emitted as an ICMP loopback packet.

There is no message queue because localhost was sitting there doing nothing.


NULL

A broken/missing structural representation is a NULL-like observation failure.

Examples include:

  • missing reality symlink
  • dangling relationship
  • missing xattr
  • changed metadata that prevents coherent reconstruction

You may see values such as:

[NULL: FileNotFoundError]

That is not a friendly error message. It is a database value having a bad day.


CLI quick reference

Initialize:

./run.sh init

Register/login:

./run.sh register alice swordfish
./run.sh auth alice swordfish

Create/list/show:

./run.sh post General alice "Hello" "There is no canonical copy of this sentence."
./run.sh list General
./run.sh show General POST_ID

Reply/like:

./run.sh reply General POST_ID alice "reply body"
./run.sh like General POST_ID bob

Inspect the crime scene:

./run.sh inspect General POST_ID
./run.sh whoisadmin
./run.sh schema
./run.sh random 100
./run.sh online

Start browser mode:

./run.sh serve 0.0.0.0 8666

Roadmap: how we got here

v0.1 - Compiler Error Forum

The first ERRORUM proved the core executable-format joke.

The runtime was encoded inside intentional GCC #error diagnostics. boot.py compiled something that was required to fail, parsed stderr, reconstructed the program, placed it into a Linux memfd, and executed it without shipping a normal runtime source file.

Storage was already wrong:

  • posts were directories
  • author identity depended on inode information
  • titles used symlink targets
  • bodies lived in xattrs
  • likes were symlinks
  • normal tree output could not reveal the real body

It was cursed, but most fields still had one obvious weird representation.

v0.2 - SchrödingerDB

v0.2 removed that remaining dignity.

Post fields became observations reconstructed from multiple mutually dependent representations plus filesystem metadata. It added the wider ambient-system architecture:

  • usernames require encoded directory name + inode
  • admin determined by distance to Unix time
  • GNU Make authentication
  • filesystem-derived authentication key
  • PID + ephemeral port + inode post IDs
  • DNS-dependent ordering
  • mkdir transaction locking
  • sessions represented by PIDs and /proc
  • ICMP notifications
  • free-disk-space randomness
  • mounted-filesystem schema version
  • permission bits as data
  • timestamps as data
  • header-capitalization API encoding
  • HTTP status codes treated as application values
  • kernel page cache accepted as the cache layer
  • dangling relationships interpreted as NULL-like state

v0.2 was usable from the command line but had no public browser frontend.

v0.3 - Browser Hell

v0.3 makes the forum actually handable to other humans.

It adds a browser observer while deliberately keeping the backend architecture intact:

  • public HTTP server
  • self-service web registration
  • web login through GNU Make authentication
  • cookie sessions whose actual existence is a living PID
  • category/thread pages
  • web thread creation
  • web replies
  • web likes
  • current rotating admin shown live
  • schema and DNS state shown live
  • weird state encoded into HTTP headers
  • systemd installer
  • Tor hidden-service deployment instructions

The important design rule is that the web layer does not normalize the storage engine. It merely observes it.

Future crimes

Possible later versions could make unrelated filesystem activity participate directly in post observation, add SSE/live updates backed by FIFOs or signals, encode moderation into ACLs, make search a find + getfattr query planner, and expose a deliberately cursed API whose request parameters are encoded primarily in header capitalization.

At that point a log rotation elsewhere on the machine could potentially alter what the forum observes. v0.3 intentionally stops just short of that because users still need to be able to read the thing.


Backups

A normal copy can destroy ERRORUM semantics even when every visible byte looks present.

You need to preserve as much filesystem identity/metadata as possible:

  • xattrs
  • permissions
  • symlinks
  • timestamps
  • ownership where relevant

Even then, inode identity and filesystem-derived keys may differ after restore.

A Linux tar attempt should at minimum use options such as:

sudo tar --xattrs --acls --numeric-owner -cpf errorum-backup.tar /var/lib/errorum

That is still not a promise that authentication or inode-derived identities will survive restoration onto another filesystem.

For maximum fidelity, filesystem/block-level snapshots are more appropriate than a logical file copy.

This is a warning, not a backup guarantee.


Manual inspection

Try:

tree hell
getfattr -d -m - hell/categories/General/posts/*
ls -lai hell/users

tree cannot show the canonical post body because there isn't one.

cat continues to be remarkably unhelpful.


Development / rebuilding the failed executable

forge.py is included as a development tool. The distributable intentionally does not include program.py.

During development:

./forge.py program.py errors.c

Then update EXPECTED_BYTES and EXPECTED_SHA256 inside boot.py, remove program.py, and test the package again.

If you accidentally distribute the reconstructed runtime as normal source, the forum will still run, but spiritually you have failed.


Smoke test

Run:

./smoke-test.sh

It verifies core CLI operations. If curl is installed, v0.3 also boots the browser frontend on loopback, registers a web user, creates a thread over HTTP, follows the resulting thread URL, and verifies the observed body.

Expected final line:

ERRORUM_0_3_SMOKE_OK

Security reality

ERRORUM is intentionally bizarre, but the browser interface still tries not to make the obvious web mistakes part of the joke:

  • output is HTML-escaped
  • browser cookies are HttpOnly and SameSite=Lax
  • session cookies are signed
  • request bodies are size-limited
  • usernames/categories are restricted character sets

However, this is still experimental software with an intentionally hostile storage model.

There is no built-in TLS, no claim of production security, no promise that data survives restore, and no reason to place sensitive information in it.

Use HTTPS through a reverse proxy for normal public Internet deployment, or keep it loopback-only behind a Tor onion service.


Design statement

A conventional forum asks:

What does the database row say?

ERRORUM asks:

Given the inode, permission bits, timestamps, xattrs, symlink targets, mount state, resolver state, process table, free disk space and current Unix time, what does reality appear to believe this post is right now?

The browser is only an observer.

That is SchrödingerDB.

See what people are saying

Contributors

Languages

C

92.3%

Shell

5.0%

Python

2.7%