ESC in INSERT mode used to switch straight to NORMAL. Now it follows the same arrow-key probe COMMAND mode already does: if the next two bytes are "[A" or "[B", treat as Up / Down and walk through the last 16 messages this client has sent. A plain ESC still falls through to NORMAL — the 50 ms probe timeout keeps that path responsive. Storage: new client_t fields char insert_history[16][MAX_MESSAGE_LEN]; int insert_history_count; int insert_history_pos; Recording: every Enter that broadcasts a message pushes the input buffer onto the ring (with FIFO eviction at 16) and resets pos. Down at the bottom of the ring returns to an empty input. This is the standard chat-client recall that vim users (and anyone who's ever used a shell) expect. |
||
|---|---|---|
| .github/workflows | ||
| docs | ||
| include | ||
| scripts | ||
| src | ||
| tests | ||
| .gitignore | ||
| install.sh | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
| tnt.1 | ||
| tnt.service | ||
TNT - Terminal Network Talk
A minimalist terminal chat server with Vim-style interface over SSH.
Features
- Zero config - Download and run, auto-generates SSH keys
- SSH-based - Leverage mature SSH protocol for encryption and auth
- Vim-style UI - Modal editing (INSERT/NORMAL/COMMAND)
- UTF-8 native - Full Unicode support
- High performance - Pure C, multi-threaded, sub-100ms startup
- Secure - Rate limiting, auth failure protection, input validation
- Persistent - Auto-saves chat history
- Elegant - Flicker-free TUI rendering
Quick Start
Installation
One-liner:
curl -sSL https://raw.githubusercontent.com/m1ngsama/TNT/main/install.sh | sh
From source:
git clone https://github.com/m1ngsama/TNT.git
cd TNT
make
sudo make install
Binary releases: https://github.com/m1ngsama/TNT/releases
Running
tnt # default port 2222
tnt -p 3333 # custom port
tnt -d /var/lib/tnt
PORT=3333 tnt # via env var
Connecting
ssh -p 2222 chat.m1ng.space
Anonymous access by default: Users can connect with ANY username/password (or empty password). No SSH keys required. Perfect for public chat servers.
Usage
Keybindings
INSERT mode (default)
ESC - Enter NORMAL mode
Enter - Send message
Backspace - Delete character
Ctrl+W - Delete last word
Ctrl+U - Delete line
Ctrl+C - Enter NORMAL mode
NORMAL mode
i - Return to INSERT mode
: - Enter COMMAND mode
j/k - Scroll down/up one line
Ctrl+D/U - Scroll half page down/up
Ctrl+F/B - Scroll full page down/up
g/G - Jump to top/bottom
? - Show help
Ctrl+C - Exit chat
COMMAND mode
:list, :users - Show online users
:nick <name> - Change nickname
:msg <user> <text> - Whisper to user
:w <user> <text> - Short alias for :msg
:last [N] - Show last N messages from history (max 50, default 10)
:search <keyword> - Search full message history (case-insensitive)
:mute-joins - Toggle join/leave system notifications
:help - Show available commands
:clear - Clear command output
:q, :quit, :exit - Disconnect
Up/Down - Browse command history
ESC - Return to NORMAL mode
Special messages (INSERT mode)
/me <action> - Send action (e.g. /me waves)
@username - Mention user (bell + highlight)
Security Configuration
Access control:
# Require password
TNT_ACCESS_TOKEN="secret" tnt
# Bind to localhost only
TNT_BIND_ADDR=127.0.0.1 tnt
# Bind to specific IP
TNT_BIND_ADDR=192.168.1.100 tnt
# Store host key and logs in an explicit state directory
TNT_STATE_DIR=/var/lib/tnt tnt
# Show the public SSH endpoint in startup logs
TNT_PUBLIC_HOST=chat.m1ng.space tnt
Rate limiting:
# Max total connections (default 64)
TNT_MAX_CONNECTIONS=100 tnt
# Max concurrent sessions per IP (default 5)
TNT_MAX_CONN_PER_IP=10 tnt
# Max new connection attempts per IP in 60 seconds (default 10)
TNT_MAX_CONN_RATE_PER_IP=30 tnt
# Disable connection-rate and auth-failure blocking (testing only)
TNT_RATE_LIMIT=0 tnt
# Idle timeout in seconds (default 1800 = 30min, 0 to disable)
TNT_IDLE_TIMEOUT=3600 tnt
SSH logging:
# 0=none, 1=warning, 2=protocol, 3=packet, 4=functions (default 1)
TNT_SSH_LOG_LEVEL=3 tnt
Production example:
TNT_ACCESS_TOKEN="strong-password-123" \
TNT_BIND_ADDR=0.0.0.0 \
TNT_MAX_CONNECTIONS=200 \
TNT_MAX_CONN_PER_IP=30 \
TNT_MAX_CONN_RATE_PER_IP=60 \
TNT_SSH_LOG_LEVEL=1 \
tnt -p 2222
SSH Exec Interface
TNT also exposes a small non-interactive SSH surface for scripts:
ssh -p 2222 chat.m1ng.space health
ssh -p 2222 chat.m1ng.space stats --json
ssh -p 2222 chat.m1ng.space users
ssh -p 2222 chat.m1ng.space "tail -n 20"
ssh -p 2222 operator@chat.m1ng.space post "service notice"
ssh -p 2222 chat.m1ng.space post "/me deploys v2.0"
post identity: the message is attributed to the SSH login name (the user@ part of the URL, falling back to anonymous). In the default anonymous-access configuration there is no identity check, so any client can post as any name. Set TNT_ACCESS_TOKEN if you need authenticated posting.
Development
Building
make # standard build
make debug # debug build (with symbols)
make asan # AddressSanitizer build
make check # static analysis (cppcheck)
make clean # clean build artifacts
Testing
make test # run comprehensive test suite
# Individual tests
cd tests
./test_basic.sh # basic functionality
./test_security_features.sh # security features
./test_anonymous_access.sh # anonymous access
./test_connection_limits.sh # per-IP concurrency and rate limits
./test_stress.sh # stress test
Test coverage:
- Basic functionality: 3 tests
- Anonymous access: 2 tests
- Security features: 11 tests
- Stress test: concurrent connections
Dependencies
- libssh (>= 0.9.0) - SSH protocol library
- pthread - POSIX threads
- gcc/clang - C11 compiler
Ubuntu/Debian:
sudo apt-get install libssh-dev
macOS:
brew install libssh
Fedora/RHEL:
sudo dnf install libssh-devel
Project Structure
TNT/
├── src/ # source code
│ ├── main.c # entry point
│ ├── ssh_server.c # SSH server implementation
│ ├── chat_room.c # chat room logic
│ ├── message.c # message persistence
│ ├── tui.c # terminal UI rendering
│ └── utf8.c # UTF-8 character handling
├── include/ # header files
├── tests/ # test scripts
├── docs/ # documentation
├── scripts/ # operational scripts
├── Makefile # build configuration
└── README.md # this file
Deployment
systemd Service
sudo cp tnt.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable tnt
sudo systemctl start tnt
# Optional: override defaults without editing the unit
sudo tee /etc/default/tnt >/dev/null <<'EOF'
PORT=2222
TNT_BIND_ADDR=0.0.0.0
TNT_STATE_DIR=/var/lib/tnt
TNT_MAX_CONNECTIONS=200
TNT_MAX_CONN_PER_IP=30
TNT_MAX_CONN_RATE_PER_IP=60
TNT_RATE_LIMIT=1
TNT_SSH_LOG_LEVEL=0
TNT_PUBLIC_HOST=chat.m1ng.space
EOF
Docker
FROM alpine:latest
RUN apk add --no-cache libssh
COPY tnt /usr/local/bin/
EXPOSE 2222
CMD ["tnt"]
See docs/DEPLOYMENT.md for details.
Files
messages.log - Chat history (RFC3339 format)
host_key - SSH host key (auto-generated, 4096-bit RSA)
motd.txt - Message of the Day (optional, shown to users on connect)
tnt.service - systemd service unit
MOTD (Message of the Day)
Place a motd.txt file in the state directory to show a welcome message to every user on connect. Users see the MOTD before entering the chat and press any key to continue.
# Example (assuming default state dir)
cat > motd.txt <<'EOF'
Welcome to the chat server!
Be respectful. No spam.
EOF
Delete motd.txt to disable the MOTD.
Documentation
- Development Guide - Complete development manual
- Quick Setup - 5-minute deployment guide
- Roadmap - Long-term Unix/GNU direction and next stages
- Security Reference - Security config quick reference
- Contributing - How to contribute
- Changelog - Version history
- CI/CD - Continuous integration setup
- Quick Reference - Command cheat sheet
Performance
- Startup: < 100ms (even with 100k+ message history)
- Memory: ~2MB (idle)
- Concurrency: Supports 100+ concurrent connections
- Throughput: 1000+ messages/second
Known Limitations
- Single chat room (no multi-room support yet)
- TUI displays at most 100 messages at once; use
:last Nor:searchto access older history from disk - Ctrl+W only recognizes ASCII space as word boundary
Contributing
Contributions welcome! See CONTRIBUTING.md
Process:
- Fork the repository
- Create feature branch (
git checkout -b feature/AmazingFeature) - Commit changes (
git commit -m 'Add some AmazingFeature') - Push to branch (
git push origin feature/AmazingFeature) - Open Pull Request
License
MIT License - see LICENSE
Acknowledgments
- libssh - SSH protocol implementation
- Linux kernel community - Code style and engineering practices
Contact
- Issues: https://github.com/m1ngsama/TNT/issues
- Pull Requests: https://github.com/m1ngsama/TNT/pulls
"Talk is cheap. Show me the code." - Linus Torvalds