Lightweight, room-aware Texas Hold'em served over SSH. Players connect with an SSH client and receive a rich terminal UI to join rooms, seat (sit??) at tables, play against humans or AI, and manage persistent wallets.
- Protocol: SSH (terminal UI)
- Game: Texas Hold'em (pre-flop enforces a tiny forced bet to simulate blinds; deeper betting tweaks planned)
- AI: pluggable AI that uses a simple fallback strategy and can call an external OpenAI-compatible endpoint when configured (see .env.example)
- Persistence: SQLite via
poker.database(wallets, transactions, actions, bonuses, AI respawns, SSH key registry, guest account resets, health history) - Accounts: SSH keys are auto-registered per username, and guest/guest1/guest2… logins are sandboxed with automatic daily resets
- Healthcheck: small HTTP probe service that verifies SSH reachability, logs results to SQLite, and exposes
/health
The codebase is organised into several modules: an SSH server and terminal UI (poker/ssh_server.py), the main game engine and logic (poker/game.py, poker/game_engine.py), room management (poker/rooms.py), player and wallet management (poker/player.py, poker/wallet.py), AI support (poker/ai.py), terminal rendering/UI (poker/terminal_ui.py), and persistent storage/database handling (poker/database.py). More modules provide features like health checks, backups, and SSH session management. See the poker/ directory for details on all components.
Want to try the game right away? Connect to the public demo server:
ssh play.poker.qincai.xyzImportant
If you haven’t used SSH before, you’ll need an SSH keypair on your machine.
Generate one with:
ssh-keygen -t ed25519 -N ""
(Press ENTER at all prompts.)
If you see “Permission denied (publickey)” when connecting, check:
- You are connecting as your own username (not impersonating another user).
- No one else has previously connected with your username.
- Permissions on your
~/.sshdirectory are set to700, and your key files to600. - If you are still having issues, try connecting using a different username:
ssh <different_username>@play.poker.qincai.xyz - Or if you are too lazy to set up SSH keys, try:
ssh guest@play.poker.qincai.xyz
Your SSH username will be used as your in-game name. This is a public demo instance — expect ephemeral data and occasional resets of database (oh and downtime).
-
Create a Python virtualenv and install dependencies listed in
requirements.txt(tested with Python 3.10+).python -m venv .venv source .venv/bin/activate python -m pip install --upgrade pip python -m pip install -r requirements.txt- Optional: copy
.env.exampleto.envto customise hostnames, ports, and AI settings (python-dotenvloads it automatically).
- Optional: copy
-
Run the server locally:
python main.py --host 0.0.0.0 --port 22222
- Use
--debugto enable debug logging.
- Use
-
Connect from any machine with an SSH client:
ssh <username>@<server-host> -p 22222
Your SSH username is used as your in-game name. Once connected you'll see a short MOTD and an interactive prompt.
Or, if you prefer to run the server inside Docker, see the PoS-Docker project for a ready-made containerised image/setup:
The Docker repo contains a Dockerfile and example docker-compose configuration to run the server and healthcheck.
The bundled HTTP healthcheck listens on port
22223by default and starts automatically alongside the SSH server.
Common interactive commands (see poker/ssh_server.py for full info):
help— show available commandsseat— claim a seat using your SSH usernamestart— start a round in the current room (requires at least one human player)players— list players in the current roomwallet— show your wallet (subcommands:history,actions,leaderboard,add,save)roomctl— room management (e.g.roomctl list,roomctl create,roomctl join <code>,roomctl share,roomctl extend,roomctl delete)togglecards/tgc— hide/show your cards for privacyquit/exit— disconnect from the server
The in-game action prompt supports fold, call, check (post-flop), bet <amount> / b <amount>. On timeouts the server will auto-fold for inactive players.
-
Environment variables can be provided via a
.envfile or system environment. Key variables used by the code:SERVER_HOST(defaultlocalhostwhen demoing or something)SERVER_PORT(default22222)HEALTHCHECK_PORT(default22223)HEALTHCHECK_INTERVAL(probe interval in seconds)AI_API_KEY,AI_API_BASE_URL,AI_MODEL,AI_TIMEOUT— configure the AI client used bypoker/ai.py
-
See .env.example for more info.
- The project uses SQLite (
poker_data.dbby default). Tables includewallets,transactions,actions,daily_bonuses,ai_respawns,ssh_keys,guest_accounts, andhealth_history. See file for details!!! - Wallets are cached in memory while players are in-game;
wallet.saveor disconnection triggers persistence. The system includes safeguards.
- A small HTTP service probes the SSH server and exposes endpoints:
/health— latest probe result (JSON)
This is started in the background by main.py via start_healthcheck_in_background(). By default it listens on port 22223, probes the SSH listener every HEALTHCHECK_INTERVAL seconds, and can log results into the SQLite health_history table when available.
- The AI will operate in two modes:
- External LLM (async OpenAI-compatible client) when
AI_API_KEYandAI_API_BASE_URLare set. - Built-in heuristic(dumb) fallback when external API is unavailable.
- External LLM (async OpenAI-compatible client) when
- The AI returns structured JSON ({
action,amount}) and the code contains parsing/validation with sensible fallbacks.AI_TIMEOUTdefaults to 5 seconds if not provided.
Need this to survive logouts? A sample user-level systemd unit lives at poker-over-ssh.service. (Make sure you have user lingering enabled with loginctl enable-linger <username>.)
mkdir -p ~/.config/systemd/user
cp poker-over-ssh.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now poker-over-ssh.serviceAdjust the paths/ports inside the unit file to match your environment (e.g. virtualenv location, port 23456 vs 22222).
This project is provided under the terms in the LICENSE file (LGPL-2.1 or later). See LICENSE for the full license text.