Architecture

What it is made of

Five small C++ servers, two React front ends and one CLI, serving nine public surfaces. Small enough to read end to end, which is the only security review a one-person project ever really gets. No Docker and no Kubernetes: a personal site should not need an orchestrator to stay up.

5 servers · 2 front ends · 1 CLI

The components

Each server owns one part of the platform and its own schema, so they start, fail and get audited separately. The ports are defaults on a machine you control.

Components · default ports
lnx-url-server
C++ · :10030
Short links, link trees, banner sets, link previews
lnx-webring-server
C++ · :10031
Ring directory, membership, next/prev/random navigation
lnx-guestbook-server
C++ · :10037
Guestbook embeds, public submit, moderation queue
lnx-widgets-server
C++ · :10039
Hit counters, polls, status pills
lnx-pages-server
C++ · :10038
Hosted homepages, block editor storage, HTML sanitizer
lnx-admin-react
React + Vite · :10032
Admin panel — everything you create, you create here
lnx-web-react
React + Vite · :10033
Public site — webring browser and hosted pages
lnx-client
C++
Terminal client for mappings — create, list, export, import

The C++ servers bind 127.0.0.1 and sit behind nginx on any deploy.

What running it needs
Host
One Debian or Ubuntu box
Storage
Postgres 14+
Deploy
Ansible + systemd
Container
None — host-native
Runtime
No Node in production
TLS
nginx in front, your certs

One command per environment

Each server applies its own embedded migrations on startup, so a deploy is the role and nothing else. There is no separate migration step to forget.

Why it is split this way

One process per kind of risk. Hosted pages are the only surface that renders markup somebody else authored; everything else emits markup the server wrote. So pages run on their own, and the sanitizer, the stored content and the render path restart and fail without the shortener’s redirect path in the same address space.

Separate schemas, one database. Each server carries its own migrations and its own version lineage over a disjoint set of tables. Adding a widget does not mean a migration that could take the short links down with it.

Nothing is reachable that doesn’t need to be. The servers listen on loopback only. One nginx vhost fronts every public surface, which is why an embed on a hosted page resolves same-origin with a relative path and no hostname is ever baked into stored content.

Host-native, on purpose. One Ansible role and systemd units onto a Debian or Ubuntu box. No containers and no orchestrator: a personal site that needs a control plane to stay up has a second thing that can be down.

What it puts on your page

Nine surfaces, from one box

The components above exist to serve a short list of things a visitor actually sees — hosted pages, link trees, rings, and widgets you paste in one line.