TNT's Not Tunnel
Find a file
m1ngsama 94f3d28562 tui: cyan ▎ gutter on the user's own messages (UX-1)
Self-messages were rendered identically to anyone else's, so scrolling
back to find "what did I just say?" meant scanning every author name.

Now every rendered chat line carries a 1-column left gutter:
- ▎ (U+258E, cyan) when the message is from the local user
- single space otherwise — keeps the rest of the line aligned

Detection handles both regular messages (username == my_username) and
/me action messages (which use "*" as the author and prefix the actor
into the content).  System messages ("系统") always render with the
space gutter regardless.

Gutter width is folded into the existing truncation budget so long
messages still fit the terminal.
2026-05-17 13:35:57 +08:00
.github/workflows feat: add SSH keepalive and CI/CD auto-deploy 2026-02-08 11:54:27 +08:00
docs chore: bug fixes and code cleanup 2026-05-16 22:44:41 +08:00
include tui: dedicated MOTD renderer (M7-5) 2026-05-17 13:16:37 +08:00
scripts feat: enhance anonymous access and long-term stability 2026-01-22 15:06:54 +08:00
src tui: cyan ▎ gutter on the user's own messages (UX-1) 2026-05-17 13:35:57 +08:00
tests fix: remove committed test binaries and add them to .gitignore 2026-04-19 18:27:34 +08:00
.gitignore fix: remove committed test binaries and add them to .gitignore 2026-04-19 18:27:34 +08:00
install.sh Add CI/CD and deployment automation 2025-12-02 12:47:15 +08:00
LICENSE Initial commit 2025-07-01 09:00:00 +08:00
Makefile fix: make integration tests advisory in CI 2026-04-19 19:01:35 +08:00
README.md chore: bug fixes and code cleanup 2026-05-16 22:44:41 +08:00
tnt.1 docs: update all docs for :last, :search, :mute-joins and MOTD 2026-04-23 12:38:04 +08:00
tnt.service refactor: stabilize SSH runtime and add exec interface 2026-03-10 18:52:20 +08:00

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

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 N or :search to access older history from disk
  • Ctrl+W only recognizes ASCII space as word boundary

Contributing

Contributions welcome! See CONTRIBUTING.md

Process:

  1. Fork the repository
  2. Create feature branch (git checkout -b feature/AmazingFeature)
  3. Commit changes (git commit -m 'Add some AmazingFeature')
  4. Push to branch (git push origin feature/AmazingFeature)
  5. Open Pull Request

License

MIT License - see LICENSE

Acknowledgments

  • libssh - SSH protocol implementation
  • Linux kernel community - Code style and engineering practices

Contact


"Talk is cheap. Show me the code." - Linus Torvalds