Architecture

What it is made of

Five small C++ servers, two React front ends and one CLI, serving ten 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 site this size 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 C++ servers bind loopback and nginx is the only thing facing the internet.

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 it runs on
Host
One Debian box
Storage
Postgres 14+
Deploy
Ansible + systemd
Container
None — host-native
Runtime
No Node in production
TLS
nginx in front, Let's Encrypt

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. We deploy it with one Ansible role and systemd units onto a Debian box. No containers and no orchestrator: a service this size that needs a control plane to stay up has a second thing that can be down.

What it puts on your page

Nine surfaces, one small stack

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.