__ _ __ / /_ ___ (_)___/ /______ ______ / __ \/ _ \/ / __ / ___/ / / / __ \ / / / / __/ / /_/ / / / /_/ / / / / \/ /_/\___/_/\__,_/_/ \__,_/_/ /_/
A Swift 6, pure-SwiftNIO Hotline-protocol server. Pairs with the Heidrun macOS client (or any classic Hotline 1.x client). Runs on macOS and Linux from the same source.
- Hotline 1.x protocol: chat, plain + threaded news, private messages + private chats, kick, broadcast
- Persistent accounts (SQLite via GRDB) with PBKDF2-SHA256 password hashing
- Admin transactions:
createLogin/deleteLogin/openLogin/modifyLogin - File system: list, info, download, upload, delete, create folder, rename, move, alias
- HTXF transfer side-channel (port + 1) with data-fork resume
- Optional TLS sibling listener pair (encrypts control + transfer end-to-end)
- Tracker UDP registration (Mobius-compatible, 5-min beacon)
- Server banner (transID 212) — JPEG / GIF / BMP / PICT / URL
- Per-file metadata (HFS type/creator + comments) persisted alongside accounts
- Idle-away supervisor — auto-flips the away flag after configurable inactivity
- Server-side chat commands —
/version,/who,/topic,/broadcast,/kick,/usershistory,/away,/me, … - Structured logging via swift-log, graceful shutdown on SIGINT / SIGTERM
make help # list all targets
make up # docker compose up -d --build (alias: make prod)
make logs # follow the container log
make downDefaults: control port 5500, HTXF transfer 5501, admin admin / CHANGE_ME_BEFORE_FIRST_RUN (see docker-compose.yml to set a real password before the first run).
Nothing inside a running container updates itself. make refresh rebuilds the image without the layer cache so the runtime picks up Ubuntu's current security fixes; run it from a weekly cron — see docs/OPERATIONS.md.
swift run HeidrunServerBinds 127.0.0.1:5500 + 127.0.0.1:5501. Default admin is admin / admin against a fresh database — change it immediately for any real deployment.
Two layered sources: env vars > TOML file > built-in defaults.
The annotated source of truth for the config surface is heidrun-server.example.toml. The high-traffic env vars:
| Var | Default | What it does |
|---|---|---|
HEIDRUN_CONFIG |
(unset) | Path to a TOML config file |
HEIDRUN_PORT |
5500 |
Control port. Transfer port is always port + 1 |
HEIDRUN_SERVER_NAME |
Heidrun |
Display name in the user list |
HEIDRUN_AGREEMENT |
(unset) | Banner text pushed after login (transID 109) |
HEIDRUN_CHAT_SUBJECT |
(empty) | Initial public chat topic |
HEIDRUN_DB_PATH |
(in-memory) | SQLite file for accounts + file metadata |
HEIDRUN_AUDIT_LOG_ENABLED |
on |
Master switch for the audit log (presence/transfers/auth/admin → a separate audit_events SQLite file, read via /audit). 0/false/no/off disables all recording. Replaces HEIDRUN_USER_HISTORY |
HEIDRUN_AUDIT_DB_PATH |
(<db>.audit.sqlite) |
Audit SQLite file; unset derives a sibling of HEIDRUN_DB_PATH (in-memory if that's also unset) |
HEIDRUN_AUDIT_RETENTION_DAYS |
90 |
Days of audit history to keep (prune-on-write) |
HEIDRUN_LOG_IP_ADDRESSES |
off |
1/true/yes/on stores each client's raw IP in audit rows. Off by default — raw IPs are GDPR personal data |
HEIDRUN_USER_HISTORY |
on |
Deprecated alias of HEIDRUN_AUDIT_LOG_ENABLED; still honoured |
HEIDRUN_SEND_USER_ACCESS |
off |
1/true/yes/on pushes the HXD-style User Access bitmap (TX 354) after login so third-party clients light up admin UI. Off by default — it blanks the roster on Heidrun clients older than protocol rc18 |
HEIDRUN_FILES_ROOT |
(tempdir) | Directory the file ops operate on |
HEIDRUN_NEWS_PATH |
(in-memory) | JSON snapshot file for news state |
HEIDRUN_ADMIN_LOGIN / HEIDRUN_ADMIN_PASSWORD |
admin / admin |
Bootstrap admin (only seeded on a fresh DB) |
HEIDRUN_TRACKERS |
(empty) | Comma-separated host[:port][:password] list of trackers |
HEIDRUN_TLS_PORT / _CERTIFICATE / _PRIVATE_KEY |
(unset) | Enables the TLS sibling pair on tls_port / tls_port + 1 |
HEIDRUN_BANNER_PATH / HEIDRUN_BANNER_KIND |
(unset) / jpeg |
Image file delivered via downloadBanner (transID 212) |
HEIDRUN_LOG_LEVEL |
info |
swift-log level: trace / debug / info / … / critical |
Operator depth (TLS / Let's Encrypt deploy hooks, ufw-docker, bootstrap admin permission upgrade, tracker debugging, full env reference) lives in docs/OPERATIONS.md. To run the full-featured heidrun-admin CLI natively on the host against a bind-mounted deployment, see docs/native-host-admin.md.
Users can issue server commands by typing them into the public chat input. Slash-prefixed lines are intercepted server-side; only the sender sees the response.
| Command | What it does |
|---|---|
/version |
Version, build id, Swift compiler, platform, ports, uptime, user count |
/uptime |
One-liner: *** uptime: 4d 11h 23m |
/who / /users |
Roster dump with nicknames + socket IDs |
/whoami |
Self-info: nickname, socket, login, IP, client version, TLS yes/no, admin yes/no, away yes/no, raw privilege bitmask |
/away |
Toggle the away flag; broadcasts userChanged |
/me <action> |
IRC-style action chat |
/broadcast <msg> |
Server-wide popup (transID 355). Admin-only (.canBroadcast) |
/topic [text] |
Read or set the public chat topic. Set is admin-only |
/kick <socketID> |
Disconnect a target. Admin-only (.disconnectUsers) |
/news clear flat|threaded|all |
Wipe news on the live server. Admin-only (.deleteNewsBundles) |
/usershistory [hours] / /history |
User join/leave history for the last 1–24h (default 1h). Admin-only (.disconnectUsers) |
/audit [--type …] [--user …] [--since Nh|Nd] [--limit N] |
Query the audit log (presence/transfers/auth/admin); /audit help for usage. Aliases /transfers, /authlog, /adminlog. Admin-only (.disconnectUsers) |
/invisible / /visible |
Hide / re-show in peer rosters. Admin-only |
/help |
List every command with one-line description |
Single / prefix only — //foo and a bare / fall through as normal chat. Case-insensitive on the command head.
| Image | Status |
|---|---|
swift:6.3.3-noble (Ubuntu 24.04) |
Builds + runs — the builder the shipped Dockerfile uses; runtime is plain ubuntu:noble with the Swift stdlib linked statically |
swift:6.2-jammy (Ubuntu 22.04) |
Builds + runs |
swift:6.2-noble (Ubuntu 24.04) |
Builds + runs |
swift:6.0-jammy |
Too old — swift-log 1.12+ requires Swift 6.2 |
HeidrunServer (lifecycle actor) binds the listener pair and owns the shared stores. Each accepted connection becomes a ClientSession actor that decodes Hotline packets via HeidrunCore and dispatches them across ClientSession+*.swift extensions (Admin, Files, PlainNews, ThreadedNews, PrivateChats, PrivateMessages, Banner, Misc). Replies are built with static PacketEncoder builders.
Wire types and codecs live in the shared SPM package franckjej/heidrun-protocol. Edits to the wire format happen there, not here.
For protocol details (encoding quirks, transaction IDs, HTXF framing, 1904-epoch timestamps) see AGENTS.md or the heidrun-protocol README.
GPL-2.0. Full text in LICENSE.
Copyright © Daubit & Francke GmbH. The copyright holder reserves all rights to license this code under other terms — commercial, proprietary, BSD/MIT-style, or any other arrangement — for its own products and for third parties on request. The GPL-2.0 grant above governs public/community use; it does not bind the copyright holder's re-use of the same code under different terms.
For a non-GPL licence: jens.francke@daubit-francke.de.
Built on Apple's Apache 2.0 Swift packages: swift-nio, swift-nio-ssl, swift-log, swift-crypto. Persistence via GRDB.swift (MIT). TOML parsing via TOMLKit (MIT).
Heidrun is a Swift 6 reimplementation of the 2002 Hotline Mac client by Göran Granström, whose original plug-in modules were GPL-2.0. This server is a new ground-up implementation but shares the protocol heritage and the same licensing posture.