A browser-based terminal that works as a messenger. No mouse, no buttons: registration, friends, private and group chats, settings — everything is done with commands typed into a command line. Messages arrive in real time over WebSocket, and messages are encrypted at rest.
Stack: Python 3.12 · FastAPI · SQLAlchemy 2 (async) · PostgreSQL · Redis · Docker.
Detailed developer documentation: ABOUT_PROJECT_ENG.md. Русское описание проекта: README_RU.md.
- Two-step login:
/login <nick>→/password <password>. - Friend requests with confirmation:
/adduser→/accept//reject. - Private and group chats with live notifications and unread counters.
- Message encryption at rest (Fernet), Argon2 password hashing.
- Rate limits, security headers, WebSocket origin checks.
- 17 terminal themes with CRT effects.
- No telemetry, no external requests, no cookies.
Legend: [x] done · [ ] planned. Checkmarks are updated as the project grows.
Messenger core
- Registration, two-step login, sessions in Redis.
- Private chats with live notifications and unread counters.
- Groups: create, join, invite, kick, rename, leave, delete.
- Friend requests with
/accept//reject. - Settings (profile, theme,
/language), security view,/delete_account. -
/clear_chatwipes a chat history while the chat keeps working. - Blocklist with confirmation:
/block(via/y) and/unblock. -
notificationssetting: live message notifications on/off, throttled to one per sender every 5 minutes. - Random greeting on login and online status in the friend list.
- 37 automated tests passing (
make test) + 77 E2E scenarios against the live HTTP/WebSocket stack (every command covered: auth, friends, chats, groups, blocks, settings, system).
Infrastructure & quality
- Alembic migrations instead of
create_all. - CI pipeline: pytest on GitHub Actions.
- E2E test for the launcher (
start.sh/start.bat/make start) on all three OS.
Next features
- Message editing and deletion (with notifications).
- Typing indicator and read receipts.
- Group admin transfer.
- Client-side localization driven by
/language. - Full-text search across chats (
/search). - Multi-process scaling via Redis Pub/Sub (horizontal).
Only one thing matters: either Docker (with Docker Compose), or Python 3.11+ with Redis installed. PostgreSQL is optional — SQLite works fine locally.
The project always runs as three separate services (that is normal):
| Service | What it is |
|---|---|
app |
the terminal itself — a FastAPI (uvicorn) process |
db |
PostgreSQL database (SQLite is fine for local development) |
redis |
a separate server for sessions, rate limits, presence |
The launcher picks the best path automatically: if Docker Compose works, it
starts all three services in containers; otherwise it runs natively (starts
Redis, creates .venv, installs deps, runs uvicorn — with SQLite if you have
no PostgreSQL).
| OS | One command |
|---|---|
| macOS | make start or ./start.sh |
| Linux | ./start.sh or make start |
| Windows | start.bat (double-click or start.bat in cmd) |
Example (macOS / Linux):
git clone https://github.com/AnonimPython/bash.social.git bash.social
cd bash.social
make startWhat make start does: creates .env from .env.example if missing, then
delegates to ./start.sh. Without Docker it falls back to the native path
and shows a hint about SQLite. Windows users just run start.bat.
Open http://localhost:8000 in a browser and register:
/register alice secret123
Install Docker for your OS:
| OS | How to install |
|---|---|
| macOS | Docker Desktop (or brew install --cask docker), or OrbStack. Free option: brew install docker docker-compose colima && colima start |
| Windows | Docker Desktop (WSL2 backend). Or winget install Docker.DockerDesktop |
| Ubuntu/Debian/Kali | sudo apt update && sudo apt install -y docker.io docker-compose-v2, then sudo usermod -aG docker $USER (log out and back in) |
| Arch | sudo pacman -S docker docker-compose and sudo systemctl enable --now docker |
Verify: docker --version and docker compose version.
Containers and ports:
| Container | Internal address | Host port |
|---|---|---|
app |
:8000 | 8000 |
db |
db:5432 | 5433 (5432 on the host is usually taken by a system PostgreSQL) |
redis |
redis:6379 | 6379 |
If a port is busy, change it in docker-compose.yml (the left part of
"host_port:container_port").
Useful commands:
docker compose ps # container states
docker compose logs -f app # follow the app logs
docker compose logs -f db # database log
docker compose down # stop (data stays in the pgdata volume)
docker compose down -v # stop and delete the database volume (all data!)
docker compose up -d --build # rebuild after code changesRedis (and ideally PostgreSQL) are installed straight into the system. Python 3.11+ is required.
brew update
brew install python@3.12 redis postgresql@16
brew services start redis
brew services start postgresql@16
createuser -P bs # enter password bs
createdb -O bs bashsocial
git clone https://github.com/AnonimPython/bash.social.git bash.social
cd bash.social
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"Run:
# with PostgreSQL:
BS_DATABASE_URL="postgresql+asyncpg://bs:bs@localhost:5432/bashsocial" make run
# or without PostgreSQL, on SQLite (Redis is still required):
./start.sh./start.sh detects no PostgreSQL and switches to SQLite automatically.
Kali is a Debian derivative, the commands are the same.
sudo apt update
sudo apt install -y python3 python3-venv python3-pip redis-server postgresql postgresql-client
sudo systemctl enable --now redis-server
sudo systemctl enable --now postgresql
sudo -u postgres createuser -P bs # enter password bs
sudo -u postgres createdb -O bs bashsocial
git clone https://github.com/AnonimPython/bash.social.git bash.social
cd bash.social
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"Run:
BS_DATABASE_URL="postgresql+asyncpg://bs:bs@localhost:5432/bashsocial" make run
# or ./start.sh — it will use SQLite, then PostgreSQL is not neededsudo pacman -Syu
sudo pacman -S python python-pip redis postgresql
sudo systemctl enable --now redis
sudo mkdir -p /var/lib/postgres/data
sudo chown -R postgres:postgres /var/lib/postgres/data
sudo -u postgres initdb -D /var/lib/postgres/data
sudo systemctl enable --now postgresql
sudo -u postgres createuser -P bs # enter password bs
sudo -u postgres createdb -O bs bashsocialThen the same as above (venv, pip install -e ".[dev]") and run with
BS_DATABASE_URL="postgresql+asyncpg://bs:bs@localhost:5432/bashsocial" make run
or ./start.sh for SQLite.
Honestly: Redis is not officially supported on Windows. Options, from most to least convenient:
- Recommended: WSL2 + Docker (see the Docker path above) — everything just works.
- Redis inside WSL2: install Ubuntu in WSL2 and follow the Ubuntu section, running the project from there too.
- Memurai — a Redis-compatible server for Windows.
- Or run only Redis in Docker and the project natively:
docker run -d --name redis -p 6379:6379 redis:7Then Python and dependencies (PowerShell):
winget install Python.Python.3.12
git clone https://github.com/AnonimPython/bash.social.git bash.social
cd bash.social
py -m venv .venv
.venv\Scripts\Activate.ps1
pip install -e ".[dev]"PostgreSQL (if you want it instead of SQLite): install from https://www.postgresql.org/download/windows/ and create the user/database during setup. Or just use SQLite:
$env:BS_DATABASE_URL = "sqlite+aiosqlite:///./dev.db"
$env:BS_REDIS_URL = "redis://localhost:6379/0"
uvicorn app.main:app --port 8000Simplest on Windows is still Docker Desktop.
For a VPS: Redis and PostgreSQL are system services, the application is a
systemd unit. Example for Ubuntu/Debian/Kali (on Arch replace apt with
pacman and adjust service names).
sudo apt update
sudo apt install -y git python3 python3-venv redis-server postgresql postgresql-client
sudo systemctl enable --now redis-server
sudo systemctl enable --now postgresql
sudo -u postgres createuser -P bs
sudo -u postgres createdb -O bs bashsocial
sudo git clone https://github.com/AnonimPython/bash.social.git /opt/bash.social
cd /opt/bash.social
sudo python3 -m venv .venv
sudo .venv/bin/pip install -e ".[dev]"Create .env with real production values (your own DB password and encryption
key are mandatory):
cd /opt/bash.social
sudo cp .env.example .env
sudo chown -R $USER .venv .env # so you do not need sudo to run
# edit .env: BS_DATABASE_URL, BS_ENCRYPTION_KEY, BS_ALLOWED_ORIGINSCreate /etc/systemd/system/bashsocial.service:
[Unit]
Description=bash.social messenger terminal
After=network.target redis-server.service postgresql.service
[Service]
WorkingDirectory=/opt/bash.social
EnvironmentFile=/opt/bash.social/.env
ExecStart=/opt/bash.social/.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
Restart=always
RestartSec=3
User=www-data
Group=www-data
[Install]
WantedBy=multi-user.targetStart and enable it:
sudo chown -R www-data:www-data /opt/bash.social
sudo systemctl daemon-reload
sudo systemctl enable --now bashsocial
sudo systemctl status bashsocialThe app already enables HSTS when the request comes over HTTPS, so put a TLS reverse proxy in front of it. The simplest is Caddy:
sudo apt install -y caddyFile /etc/caddy/Caddyfile:
chat.example.com {
reverse_proxy 127.0.0.1:8000
}
Caddy obtains the TLS certificate automatically. Do not forget to set
BS_ALLOWED_ORIGINS=chat.example.com in .env so WebSocket connections pass
the Origin check.
| Symptom | Cause and solution |
|---|---|
port 8000 already in use |
Stop the other process or run on another port: uvicorn app.main:app --port 8001 / change "8000:8000" in compose |
port 5432 in use (native) |
A PostgreSQL already runs on the host. Use it (create a database) or change the port in BS_DATABASE_URL |
Docker: the db container does not start |
Host port 5433 is busy — change the left part of "5433:5432" in compose |
ModuleNotFoundError: cryptography |
Dependencies are not installed: pip install -e ".[dev]" |
| Messages cannot be decrypted after changing the key | The encryption key cannot be changed once data exists. Set BS_ENCRYPTION_KEY BEFORE sending the first messages |
| WebSocket fails on a custom domain | Set BS_ALLOWED_ORIGINS=your-domain in .env |
| No Redis connection | Check that redis runs and the URL is correct (redis-cli ping → PONG) |
CliError: ... request already pending |
Intended: a friend request is one-time, see /requests |
-
BS_ENCRYPTION_KEYis set (generate:python -c "import secrets; print(secrets.token_urlsafe(48))"). - A strong PostgreSQL password instead of
bs, with an updatedBS_DATABASE_URL. -
BS_ALLOWED_ORIGINSwith your domain. - HTTPS via a reverse proxy (Caddy/Nginx) — HSTS turns on automatically.
-
make testpasses. - Backups of the
pgdatavolume (or database dumps).
All settings are read from environment variables with the BS_ prefix
(app/config.py). Copy .env.example to .env and change what you need.
The most important ones:
| Variable | Default | What it does |
|---|---|---|
BS_DATABASE_URL |
postgresql+asyncpg://bs:bs@db:5432/bashsocial |
database connection (asyncpg for PostgreSQL, or sqlite+aiosqlite:///./dev.db) |
BS_REDIS_URL |
redis://redis:6379/0 |
Redis connection |
BS_ENCRYPTION_KEY |
empty (dev fallback) | must be set in production — secret for message encryption; generate with python -c "import secrets; print(secrets.token_urlsafe(48))" |
BS_ALLOWED_ORIGINS |
empty (same host only) | comma-separated allowed WebSocket origins |
BS_MAX_SESSIONS |
5 |
max active sessions per user |
BS_SESSION_TTL |
2592000 (30 days) |
session lifetime in seconds |
BS_DEFAULT_LANG |
ru |
default UI language for new accounts and guests (ru or en); each user can override with /language |
BS_DEFAULT_THEME |
kali |
default theme for new accounts (any name from /theme ls) |
BS_REGISTER_ENABLED |
true |
set to false to disable new registrations (/register then says registration is disabled) |
BS_NICK_MIN / BS_NICK_MAX |
5 / 15 |
nick length bounds |
BS_PASSWORD_MIN / BS_PASSWORD_MAX |
8 / 128 |
password length bounds |
BS_GROUP_NAME_MAX |
40 |
max group name length |
Rate limits, input limits and pagination are configured the same way
(BS_REGISTER_RATE, BS_MSG_RATE, BS_MAX_INPUT, BS_PAGE_SIZE and so on).
The full list with comments is in .env.example.
/register alice secret123 → create an account (auto-login)
/adduser bob → send a friend request
/requests → check requests · accept: /accept bob
/contact bob → open a private chat
hello! → plain text is a message in the active chat
/group create team → create a group
/theme matrix → switch the terminal theme
/help → list all commands
Every command has a built-in hint: /command help.
The full list of all commands (29), grouped the same way as /help. Aliases
are shorter synonyms — both spellings work.
| Command | Aliases | What it does |
|---|---|---|
/register <nick> <password> |
— | Creates an account and logs in immediately. |
/login <nick> |
— | Starts login; the system then asks for the password. |
/password <password> |
— | Enters the password after /login. Never shown in command history. |
/logout |
— | Ends the session. Not available inside a chat — /leave first. |
/whoami |
— | Shows info about your account. |
/whois <nick> |
whoisuser |
Public info about another user. |
| Command | Aliases | What it does |
|---|---|---|
/adduser <nick> |
— | Sends a friend request; you become friends once it is accepted. |
/accept <nick> |
— | Accepts an incoming friend request. |
/reject <nick> |
deny |
Declines an incoming friend request. |
/requests |
req |
Shows incoming and outgoing friend requests. |
/deluser <nick> |
— | Removes a user from your friend list. |
/find <fragment> |
— | Searches users by a nick fragment (at least 2 chars). |
/ls <section> |
list |
Universal list: user · my_users · groups · unread. |
| Command | Aliases | What it does |
|---|---|---|
/contact <nick> |
open |
Opens a private chat and shows its history. |
/msg <text> |
— | Sends a message in the current chat. Plain text without /msg also works. |
/leave |
— | Leaves the current chat — private or group. |
/clear_chat |
wipe |
Deletes all messages of the current chat; the chat itself stays open. |
| Command | Aliases | What it does |
|---|---|---|
/mk_group <name> |
— | Creates a group and opens its chat at once. |
/group create <name> |
— | Creates a group and prints its invite code. |
/group join <code> |
— | Joins a group by invite code. |
/group invite <nick> |
— | Invites a friend (creator only). |
/group members |
— | Members of the current group. |
/group kick <nick> |
— | Removes a member (creator/admin). |
/group rename <name> |
— | Renames the group (creator only). |
/group leave |
— | Leaves the current group. |
/group delete |
— | Deletes the group with all its history (creator). |
/group <code> |
— | Opens a group by its code. |
| Command | Aliases | What it does |
|---|---|---|
/delete_account |
— | Permanently deletes the account (confirmation /y or /n). |
/block <nick> |
ban |
Blocks a user (confirmation /y). Can be done from a chat. |
/unblock <nick> |
unban |
Unblocks a user. Can be done from a chat. |
/security |
sec |
Account security state: encryption, sessions, language. |
| Command | Aliases | What it does |
|---|---|---|
/settings |
— | Shows all settings. |
/settings set <key> <value> |
— | Sets allow_messages · allow_invites · lang · notifications. |
/settings security |
— | Security details for your account. |
/theme [theme] |
— | Switches the terminal theme; /theme ls lists all themes. |
/language [language] |
lang |
Changes the interface language; without an argument lists languages. |
| Command | Aliases | What it does |
|---|---|---|
/help [command] |
— | Lists all commands, or shows help for one command. |
/clear |
cls |
Clears the terminal screen. |
/history |
— | Shows the last entered commands (up to 100). |
Flow helpers: /y / /n confirm or cancel a pending action (/block,
/delete_account). Plain text (a line without /) is sent as a message into
the active chat. Run /help <command> for details on any command.
- Passwords: Argon2 (
app/security/crypto.py). - Sessions: opaque tokens in Redis with TTL and a per-user cap.
- A password must not contain the nick.
- Rate limits on registration, login, password, commands, messages and WebSocket connections.
- Security headers on every response: CSP, X-Frame-Options: DENY, Referrer-Policy and others.
- Chat text is sanitized; message history stores no secrets or chat content.
make test # = python -m pytest -qEvery push and pull request is checked by GitHub Actions
(.github/workflows/ci.yml): it runs the full
suite on Python 3.11 and 3.12 and validates the JS client syntax with
node --check. Tests run in-memory (SQLite + fake Redis), so no services
are needed for CI. Add a badge to your fork's repo page after pushing:

app/
api/ HTTP and WebSocket entry points
cli/ parser, command registry, dispatcher, command handlers
services/ business logic: auth, sessions, users, friendship, chat, groups, settings
security/ crypto, HTTP headers, sanitization, WebSocket guards
models/ SQLAlchemy: User, Friendship, Group, GroupMember, Message, Block
static/ index.html, css, js (the terminal client)
tests/ automated tests
config.py settings (BS_ prefix)
A full file-by-file walkthrough is in ABOUT_PROJECT_ENG.md / ABOUT_PROJECT_RU.md.