A terminal-based text adventure game built with xterm.js. Experience interactive storytelling through a familiar command-line interface, with support for creating custom adventures through an administration mode. This project is 100% created with AI, Kiro specifically, and it was created for the hackaton Kiroween at DevPost (https://devpost.com/software/tag-terminal-adventure-game-on-steroids)
# Windows PowerShell
.\start.ps1
# Windows Command Prompt
start.bat
# Linux/Mac
./start.shThen open http://localhost:3000 in your browser!
📖 See STARTUP_GUIDE.md for detailed startup instructions and troubleshooting.
- Interactive Terminal Interface: Built with xterm.js for an authentic terminal experience
- Player Mode: Navigate locations, talk to characters, and explore adventures
- Admin Mode: Create and manage custom adventures with sudo-like authentication
- Persistent Database: All adventures and game progress are automatically saved to disk
- Demo Adventure: Includes "The Lost Temple" demo adventure to get started
- Node.js 18+ and npm
- Clone the repository
- Install dependencies:
npm installUse the provided startup scripts to run both frontend and backend:
Windows (PowerShell):
.\start.ps1Windows (Command Prompt):
start.batLinux/Mac:
./start.shThese scripts will:
- Check and install dependencies if needed
- Start both frontend and backend servers
- Display the URLs where the application is running
Alternatively, run both frontend and backend manually:
npm run devThis will start:
- Frontend dev server on http://localhost:3000
- Backend API server on http://localhost:3001
Build both frontend and backend:
npm run buildStart the production server:
npm startThe application will be available at http://localhost:3001
- Docker 20.10+ and Docker Compose 2.0+
Build all Docker images using the provided build scripts:
Linux/Mac:
./build-docker.shWindows (PowerShell):
.\build-docker.ps1With version tag:
./build-docker.sh 1.0.0Multi-architecture build:
./build-docker.sh 1.0.0 --platform linux/amd64,linux/arm64The build script will create three Docker images:
terminal-adventure-game- Application container (frontend + backend)adventure-game-nginx- Nginx reverse proxy with ModSecurity WAFadventure-game-fail2ban- Fail2ban intrusion prevention
To export images for deployment on another Docker host:
Linux/Mac:
./export-docker-images.shWindows (PowerShell):
.\export-docker-images.ps1This creates a .tar file containing all three images. The file will be named with a timestamp (e.g., terminal-adventure-game-20231204_153045.tar).
Transfer and import on remote host:
- Transfer the tar file:
scp terminal-adventure-game-*.tar user@remote-host:/path/to/destination/- Import images on remote host:
docker load -i terminal-adventure-game-*.tar- Copy configuration files to remote host:
# Use docker-compose.prod.yml for pre-built images (no source code needed)
scp docker-compose.prod.yml .env user@remote-host:/path/to/app/- Start services on remote host:
cd /path/to/app
docker compose -f docker-compose.prod.yml up -dNote: Use docker-compose.prod.yml on remote hosts where you've imported pre-built images. The regular docker-compose.yml is for building from source.
Alternative: Export individual images
If you need to export images separately:
# Export individual images
docker save -o app.tar terminal-adventure-game:latest
docker save -o nginx.tar adventure-game-nginx:latest
docker save -o fail2ban.tar adventure-game-fail2ban:latest
# Import on remote host
docker load -i app.tar
docker load -i nginx.tar
docker load -i fail2ban.tar- Copy the environment file:
cp .env.example .env- Edit
.envand configure your settings:
# Change the admin password (IMPORTANT!)
ADMIN_PASSWORD=your-secure-password
# Configure LM Studio endpoint if using AI NPCs
LMSTUDIO_BASE_URL=http://host.docker.internal:1234
# Adjust security settings as needed
FAIL2BAN_BANTIME=3600
RATE_LIMIT=10r/s- Start the application:
docker compose up -d- Access the application: Open http://localhost:8888 in your browser
Application Configuration:
PORT- HTTP server port (default: 3001)ADMIN_PASSWORD- Admin password for sudo command (default: admin123)
LM Studio AI Configuration:
LMSTUDIO_BASE_URL- LM Studio API endpoint (default: http://192.168.0.18:1234)LMSTUDIO_MODEL- AI model name (default: default)LMSTUDIO_TIMEOUT- Request timeout in ms (default: 30000)LMSTUDIO_TEMPERATURE- AI temperature 0.0-1.0 (default: 0.8)LMSTUDIO_MAX_TOKENS- Max tokens in response (default: 150)
Nginx Configuration:
NGINX_PORT- External HTTP port (default: 80)RATE_LIMIT- Requests per second per IP (default: 10r/s)CLIENT_MAX_BODY_SIZE- Max upload size (default: 10M)
Fail2ban Configuration:
FAIL2BAN_BANTIME- Ban duration in seconds (default: 3600)FAIL2BAN_FINDTIME- Time window for counting attempts (default: 600)FAIL2BAN_MAXRETRY- Max failed attempts before ban (default: 5)
See .env.example for detailed documentation and examples.
Game data is persisted in Docker volumes:
game-data- Database and game statenginx-logs- Nginx access and error logsfail2ban-data- Fail2ban ban state
Backup game data:
docker run --rm -v game-data:/data -v $(pwd):/backup alpine tar czf /backup/game-data-backup.tar.gz -C /data .Restore game data:
docker run --rm -v game-data:/data -v $(pwd):/backup alpine tar xzf /backup/game-data-backup.tar.gz -C /dataView logs:
docker compose logs -f # All services
docker compose logs -f app # Application only
docker compose logs -f nginx # Nginx only
docker compose logs -f fail2ban # Fail2ban onlyStop the application:
docker compose downStop and remove volumes (WARNING: deletes all data):
docker compose down -vRestart services:
docker compose restartThe Docker deployment includes multiple security layers:
Internet → nginx:80 → ModSecurity WAF → app:3001
↓
fail2ban (monitors logs)
- OWASP Core Rule Set (CRS) - Industry-standard protection against common attacks
- Paranoia Level 1 - Balanced protection with minimal false positives
- Anomaly Scoring - Blocks requests exceeding threat threshold
- Protection Against:
- SQL Injection
- Cross-Site Scripting (XSS)
- Path Traversal
- Remote Code Execution
- Command Injection
Configuration: nginx/modsecurity/modsecurity.conf and nginx/modsecurity/crs-setup.conf
Automatically bans IPs that exhibit malicious behavior:
- nginx-auth jail - Monitors 401/403 responses (failed authentication)
- nginx-limit-req jail - Monitors rate limit violations
- app-auth jail - Monitors failed login attempts to
/api/auth/login
Default Settings:
- Ban time: 1 hour
- Find time: 10 minutes
- Max retry: 5 attempts
Check banned IPs:
docker compose exec fail2ban fail2ban-client status nginx-authManually unban an IP:
docker compose exec fail2ban fail2ban-client set nginx-auth unbanip <IP_ADDRESS>Configuration: fail2ban/jail.local and fail2ban/filter.d/*.conf
Nginx adds security headers to all responses:
X-Frame-Options: DENY- Prevents clickjackingX-Content-Type-Options: nosniff- Prevents MIME sniffingX-XSS-Protection: 1; mode=block- Enables XSS filterReferrer-Policy: no-referrer-when-downgrade- Controls referrer information
Nginx limits requests to prevent abuse and DoS attacks:
- Default: 10 requests per second per IP
- Burst: 20 requests (allows temporary spikes)
- Configurable via
RATE_LIMITenvironment variable
- Frontend network - Nginx to internet (bridge)
- Backend network - Nginx to app (internal, no internet access)
- Only nginx port 80 is exposed to the host
- Change default passwords - Update
ADMIN_PASSWORDin.env - Use strong passwords - 12+ characters, mixed case, numbers, symbols
- Monitor logs regularly -
docker compose logs -f - Keep images updated - Rebuild periodically for security patches
- Adjust fail2ban settings - Based on your traffic patterns
- Lower rate limits if under attack - Temporarily reduce
RATE_LIMIT - Backup data regularly - Use volume backup commands above
If port 80 is already in use, change the nginx port:
# In .env file
NGINX_PORT=8080Then access the application at http://localhost:8080
Volume mount permissions:
# Fix permissions on game data directory
docker compose down
docker volume rm game-data
docker compose up -dFail2ban iptables errors:
Fail2ban requires NET_ADMIN and NET_RAW capabilities. These are already configured in docker-compose.yml.
Reset database to fresh state:
docker compose down
docker volume rm game-data
docker compose up -dView database contents:
docker compose exec app ls -la /app/backend/dataLM Studio not reachable:
If LM Studio is running on the host machine:
# In .env file
LMSTUDIO_BASE_URL=http://host.docker.internal:1234If LM Studio is on another machine:
# In .env file
LMSTUDIO_BASE_URL=http://<IP_ADDRESS>:1234Check service health:
docker compose psAll services should show "healthy" status.
Check nginx logs:
docker compose logs nginxCheck app logs:
docker compose logs appIf legitimate requests are being blocked by ModSecurity:
- Check ModSecurity logs:
docker compose logs nginx | grep ModSecurity- Adjust paranoia level in
nginx/modsecurity/crs-setup.conf:
setvar:tx.paranoia_level=1 # Lower = fewer false positives
- Add rule exclusions for specific endpoints if needed
Check fail2ban status:
docker compose exec fail2ban fail2ban-client statusCheck jail status:
docker compose exec fail2ban fail2ban-client status nginx-authVerify log format matches filter patterns in fail2ban/filter.d/*.conf
App health check failing:
# Check if app is responding
docker compose exec app wget -O- http://localhost:3001/api/healthNginx health check failing:
# Check if nginx can reach app
docker compose exec nginx wget -O- http://app:3001/api/healthmove <direction>- Move to an adjacent location (north, south, east, west, up, down)look- Examine your current surroundingstalk <character>- Speak with a characteradventures- List all available adventuresload <adventure-id>- Load and play a different adventurehelp- Display all available commandshelp <command>- Get detailed help for a specific commandclear- Clear the terminal screenexit- Exit the game
First, enter admin mode:
sudoThen use these commands:
create-adventure <name>- Create a new adventureadd-location <name> <description>- Add a location to the adventureadd-character <name> <dialogue...>- Add a character to the current locationconnect <from-id> <to-id> <direction>- Connect two locationssave- Save the current adventurelist-adventures- Show all adventuresexit- Return to player mode
The default admin password is: admin123
Important: Change this in production by modifying the password hash in the database initialization.
- Frontend: TypeScript + Vite + xterm.js
- Backend: Node.js + Express + SQLite (sql.js with file persistence)
- Monorepo: Managed with npm workspaces
The game uses SQLite for data storage with automatic file persistence. All adventures, game states, and admin data are saved to disk and survive server restarts.
The database file is stored at:
backend/data/game.db
This file is automatically created on first startup and updated after every write operation (creating adventures, saving game state, etc.).
You can customize the database location using the DB_PATH environment variable:
# Linux/Mac
export DB_PATH=/path/to/custom/database.db
npm start
# Windows PowerShell
$env:DB_PATH="C:\path\to\custom\database.db"
npm start
# Windows Command Prompt
set DB_PATH=C:\path\to\custom\database.db
npm start- On Startup: The server checks for an existing database file
- If found: Loads the database from disk (preserving all data)
- If not found: Creates a new database with demo adventure
- During Operation: Every write operation (create/update/delete) automatically saves to disk
- Data Preserved: Adventures, game states, admin credentials, and all game progress persist across restarts
Backup Your Database:
# Create a backup
cp backend/data/game.db backend/data/game.db.backup
# Restore from backup
cp backend/data/game.db.backup backend/data/game.dbReset to Fresh Database:
# Delete the database file (server will create a new one on next startup)
rm backend/data/game.db # Linux/Mac
del backend\data\game.db # WindowsView Database Contents: You can use any SQLite browser tool to inspect the database file, such as:
terminal-adventure-game/
├── frontend/ # Frontend application
│ ├── src/
│ │ ├── admin/ # Administration system
│ │ ├── api/ # API client
│ │ ├── auth/ # Authentication manager
│ │ ├── engine/ # Game engine
│ │ ├── help/ # Help system
│ │ ├── parser/ # Command parser
│ │ ├── terminal/ # Terminal interface
│ │ └── main.ts # Entry point
│ └── index.html
├── backend/ # Backend API server
│ ├── data/ # Database storage (gitignored)
│ │ └── game.db # SQLite database file
│ └── src/
│ ├── api/ # Express server
│ ├── auth/ # Authentication
│ ├── database/ # Data store, schema, and persistence
│ └── index.ts # Entry point
└── package.json # Root package.json
MIT, TypeScript, and Express.
terminal-adventure-game/
├── frontend/ # Frontend application (Vite + TypeScript + xterm.js)
│ ├── src/
│ ├── package.json
│ └── tsconfig.json
├── backend/ # Backend API server (Express + TypeScript + SQLite)
│ ├── src/
│ ├── package.json
│ └── tsconfig.json
└── package.json # Root workspace configuration
Install dependencies:
npm installRun both frontend and backend in development mode:
npm run devOr run them separately:
npm run dev:frontend # Frontend on http://localhost:3000
npm run dev:backend # Backend on http://localhost:3001Build both frontend and backend:
npm run buildStart the production server:
npm start- Frontend: TypeScript, Vite, xterm.js
- Backend: Node.js, Express, TypeScript
- Database: SQLite with sql.js
- Authentication: bcryptjs for password hashing