lemoniscate [options]
| Flag | Long | Argument | Default | Description |
|---|---|---|---|---|
-i |
--interface |
ADDR | all | IP address to listen on |
-p |
--port |
PORT | 5500 | Base port (transfers on PORT+1) |
--bind |
PORT | 5500 | Alias for --port (used by GUI launcher) |
|
-c |
--config |
DIR | auto | Configuration directory path |
-f |
--log-file |
PATH | stderr | Log file path (enables file logging) |
-l |
--log-level |
LEVEL | info | Log level: debug, info, error |
--init |
Initialize a default config directory and exit | |||
-v |
--version |
Print version and exit | ||
-h |
--help |
Show help |
# Initialize a new config directory
./lemoniscate --init -c /path/to/config
# Start with defaults (port 5500, auto-find config)
./lemoniscate
# Start on a specific port with config directory
./lemoniscate -p 5500 -c /path/to/config
# Start with debug logging to a file
./lemoniscate -c /path/to/config -l debug -f /var/log/lemoniscate.log
# Bind to a specific interface
./lemoniscate -i 192.168.1.100 -p 5500 -c /path/to/config| Signal | Action |
|---|---|
SIGINT / SIGTERM |
Graceful shutdown |
SIGHUP |
Reload path exists, but live event-loop integration is currently incomplete |
SIGPIPE |
Ignored (prevents crash on broken client connections) |
Created by --init or manually:
config/
├── config.yaml # Server configuration
├── Agreement.txt # Login agreement shown to connecting clients
├── MessageBoard.txt # Flat message board (news)
├── Banlist.yaml # IP, username, and nickname bans
├── banner.jpg # Server banner image (optional, JPG or GIF)
├── Files/ # Shared file directory (file root)
└── Users/ # User account YAML files
├── admin.yaml
└── guest.yaml
Name: "My Hotline Server"
Description: "A Hotline server running Lemoniscate"
BannerFile: banner.jpg # Relative to config dir, or absolute path
FileRoot: Files # Relative to config dir, or absolute path
EnableTrackerRegistration: false
Trackers: [] # e.g. ["tracker.example.com:5499"]
EnableBonjour: true
Encoding: macintosh # Text encoding (macintosh = Mac Roman)
MaxDownloads: 0 # 0 = unlimited
MaxDownloadsPerClient: 0 # 0 = unlimited
MaxConnectionsPerIP: 0 # 0 = unlimited
PreserveResourceForks: false # Preserve Mac resource forks in file transfers
EnableHOPE: false # HOPE secure authentication
HOPELegacyMode: false # Allow weak MAC algorithms
HOPERequiredPrefix: "[E2E]" # Prefix for E2E-gated content
E2ERequireTLS: false # Require TLS for E2E file access
TLSCertFile: "" # Path to PEM certificate
TLSKeyFile: "" # Path to PEM private key
TLSPort: 5600 # TLS listener port
# Persistent chat history (opt-in extension)
ChatHistory:
Enabled: false # Master switch — no persistence when false
MaxMessages: 10000 # Per-channel cap; 0 = unlimited
MaxDays: 0 # Age cap in days; 0 = unlimited
LegacyBroadcast: false # Replay recent messages to non-capable clients
LegacyCount: 30 # Number replayed when LegacyBroadcast is on
# EncryptionKey: /path/to/chat-history.key # 32-byte key (optional)Login: guest
Name: Guest User
Password: "" # Empty string = no password required
Access:
DownloadFile: true
UploadFile: false
ReadChat: true
SendChat: true
CreateUser: false
DeleteUser: false
OpenUser: false
ModifyUser: false
GetClientInfo: true
DisconnectUser: false
Broadcast: false
CreateFolder: false
DeleteFile: false
OpenChat: true
NewsReadArt: true
NewsPostArt: falsebanList: {} # IP bans: {"192.168.1.100": true}
bannedUsers: {} # Login bans: {"baduser": true}
bannedNicks: {} # Nickname bans: {"troll": true}Plain text shown to users on first connection. If the file is missing or empty, no agreement is shown.
The flat message board (news). Posts are prepended to the top. Uses \r (carriage return) as line separator per the Hotline protocol.
| Port | Purpose |
|---|---|
| BASE (default 5500) | Hotline protocol (transactions, chat, user management) |
| BASE+1 (default 5501) | File transfers (HTXF protocol) |
| BASE+100 (default 5600) | TLS Hotline protocol |
| BASE+101 (default 5601) | TLS file transfers |
- TCP connection to base port
- Hotline handshake (
TRTP+ version exchange) - Client sends
TranLogin(107) with username, password, icon - Server checks ban list (IP + username)
- Server authenticates against
Users/*.yamlaccounts - Server sends
TranShowAgreement(109) if Agreement.txt exists - Client sends
TranAgreed(121) - Server sends user access bitmap
- Server broadcasts
TranNotifyChangeUser(301) to all clients - Client is now connected and can send/receive transactions
For TLS connections, clients connect to the TLS port instead. The handshake and login proceed identically but over an encrypted channel.
- Token bucket algorithm: 1 connection per 2 seconds per IP
- Excess connections are rejected with "Rate limited" log entry
- Checked every 10 seconds
- Users marked idle/away after 300 seconds (5 minutes) of inactivity
- KeepAlive and other transactions reset the idle timer
| Type | Name | Description |
|---|---|---|
| 105 | ChatSend | Send public chat message |
| 106 | ChatMsg | Relayed chat (server → clients) |
| 108 | SendInstantMsg | Private message to a user |
| 112 | InviteNewChat | Create a new private chat room |
| 113 | InviteToChat | Invite user to existing chat room |
| 114 | RejectChatInvite | Decline chat invite |
| 115 | JoinChat | Join a private chat room |
| 116 | LeaveChat | Leave a private chat room |
| 120 | SetChatSubject | Set chat room topic |
| Type | Name | Description |
|---|---|---|
| 107 | Login | Authenticate and connect |
| 121 | Agreed | Accept server agreement |
| 300 | GetUserNameList | Get list of online users |
| 301 | NotifyChangeUser | User status change broadcast |
| 302 | NotifyDeleteUser | User disconnect broadcast |
| 303 | GetClientInfoText | Get user info |
| 304 | SetClientUserInfo | Update own nick/icon |
| 348 | ListUsers | List user accounts (admin) |
| 349 | UpdateUser | Modify user account (admin) |
| 350 | NewUser | Create user account (admin) |
| 351 | DeleteUser | Delete user account (admin) |
| 352 | GetUser | Get user account details (admin) |
| 353 | SetUser | Set user account (admin) |
| 355 | UserBroadcast | Send broadcast message (admin) |
| 110 | DisconnectUser | Kick user (admin) |
| 500 | KeepAlive | Connection keepalive ping |
| Type | Name | Description |
|---|---|---|
| 101 | GetMsgs | Read the message board |
| 103 | OldPostNews | Post to the message board (prepends) |
| Type | Name | Description |
|---|---|---|
| 370 | GetNewsCatNameList | Partial: handler currently returns empty reply with access checks |
| 371 | GetNewsArtNameList | Partial: handler currently returns empty reply with access checks |
| 400 | GetNewsArtData | Partial: handler currently returns empty reply with access checks |
| 410 | PostNewsArt | Partial: handler currently returns empty reply with access checks |
| 411 | DelNewsArt | Partial: handler currently returns empty reply with access checks |
| 380 | DelNewsItem | Partial: handler currently returns empty reply with access checks |
| 381 | NewNewsFldr | Partial: handler currently returns empty reply with access checks |
| 382 | NewNewsCat | Partial: handler currently returns empty reply with access checks |
| Type | Name | Description |
|---|---|---|
| 200 | GetFileNameList | List files in directory |
| 202 | DownloadFile | Partial: transfer setup only; transfer-port data path is currently stubbed |
| 203 | UploadFile | Partial: transfer setup only; transfer-port data path is currently stubbed |
| 204 | DeleteFile | Delete file/folder |
| 205 | NewFolder | Create folder |
| 206 | GetFileInfo | Get file metadata |
| 207 | SetFileInfo | Set file metadata |
| 208 | MoveFile | Move/rename file |
| 209 | MakeFileAlias | Create file alias |
| 210 | DownloadFolder | Partial: transfer setup only; transfer-port data path is currently stubbed |
| 213 | UploadFolder | Partial: transfer setup only; transfer-port data path is currently stubbed |
| 212 | DownloadBanner | Download server banner |
There is currently no REST API. All server management is done through:
- CLI flags at startup
- Config file editing + restart (or future SIGHUP integration)
- The Lemoniscate.app GUI (which manages the server process via NSTask)
A REST API is planned for a future version.
See also: GUI reference
When EnableBonjour: true in config.yaml, the server registers as a _hotline._tcp service on the local network. Other Macs on the LAN can discover the server automatically in Hotline clients that support Bonjour browsing.
When EnableTrackerRegistration: true and trackers are listed, the server periodically registers with Hotline tracker servers so it appears in public server lists.
# Install libyaml first (via Tigerbrew or from source)
brew install libyaml # or build from source
# Build server only
make CC=gcc-4.0 \
CFLAGS="-std=c99 -Wall -Wextra -pedantic -O2 -I./include -I/usr/local/include -DTARGET_OS_MAC=1 -mmacosx-version-min=10.4" \
YAML_LDFLAGS="-L/usr/local/lib -lyaml" \
lemoniscate
# Build GUI + server + app bundle
make CC=gcc-4.0 \
CFLAGS="..." OBJCFLAGS="..." YAML_LDFLAGS="..." \
appmake all # builds libhotline.a + lemoniscate
make test # builds and runs all testsCurrent: 0.1.5
Originally inspired by Mobius by Jeff Halter.