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.
Requirements:
user.* extended attributesping only if you want the ICMP notification demoOn 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
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.
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.
The browser observer supports:
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.
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:
/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.
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.
A title/body is reconstructed from:
.clock directory mtimeNeither 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 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.
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.
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 are SHA-256 hashed, then the digest is XORed with a key derived from:
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.
There is no AUTO_INCREMENT.
A new ID combines:
Example:
18a6-a35d-17d
Other software running on the machine can indirectly influence future IDs by consuming PIDs and ports.
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.
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.
./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.
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 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.
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.
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.
A broken/missing structural representation is a NULL-like observation failure.
Examples include:
You may see values such as:
[NULL: FileNotFoundError]
That is not a friendly error message. It is a database value having a bad day.
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
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:
tree output could not reveal the real bodyIt was cursed, but most fields still had one obvious weird representation.
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:
mkdir transaction locking/procv0.2 was usable from the command line but had no public browser frontend.
v0.3 makes the forum actually handable to other humans.
It adds a browser observer while deliberately keeping the backend architecture intact:
The important design rule is that the web layer does not normalize the storage engine. It merely observes it.
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.
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:
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.
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.
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.
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
ERRORUM is intentionally bizarre, but the browser interface still tries not to make the obvious web mistakes part of the joke:
HttpOnly and SameSite=LaxHowever, 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.
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.
1 commits
Hacker News (1)
C
92.3%
Shell
5.0%
Python
2.7%
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.
Requirements:
user.* extended attributesping only if you want the ICMP notification demoOn 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
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.
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.
The browser observer supports:
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.
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:
/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.
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.
A title/body is reconstructed from:
.clock directory mtimeNeither 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 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.
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.
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 are SHA-256 hashed, then the digest is XORed with a key derived from:
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.
There is no AUTO_INCREMENT.
A new ID combines:
Example:
18a6-a35d-17d
Other software running on the machine can indirectly influence future IDs by consuming PIDs and ports.
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.
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.
./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.
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 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.
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.
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.
A broken/missing structural representation is a NULL-like observation failure.
Examples include:
You may see values such as:
[NULL: FileNotFoundError]
That is not a friendly error message. It is a database value having a bad day.
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
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:
tree output could not reveal the real bodyIt was cursed, but most fields still had one obvious weird representation.
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:
mkdir transaction locking/procv0.2 was usable from the command line but had no public browser frontend.
v0.3 makes the forum actually handable to other humans.
It adds a browser observer while deliberately keeping the backend architecture intact:
The important design rule is that the web layer does not normalize the storage engine. It merely observes it.
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.
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:
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.
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.
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.
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
ERRORUM is intentionally bizarre, but the browser interface still tries not to make the obvious web mistakes part of the joke:
HttpOnly and SameSite=LaxHowever, 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.
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.
Hacker News (1)
1 commits
C
92.3%
Shell
5.0%
Python
2.7%