6.8 KiB
L4D Tools
A Rails 8.1 application for managing Left4Dead 2 game servers with a web interface.
Features
- Steam Authentication: Login via Steam OpenID
- Server Templates: Define reusable server configurations with overlays, config options, and startup parameters
- Overlays Management: Support for both system and custom overlays with file uploads
- Server Lifecycle: Spawn, start, stop, restart, and delete L4D2 servers
- Live Logs: Stream server logs in real-time via WebSocket (ActionCable)
- Health Checks: Periodic monitoring of server status via systemd and RCON
- Activity Logging: Track user actions (server creation, deletion, start/stop)
- User-Level Systemd: Servers run as systemd user units (steam user)
System Requirements
- Ruby 4.0+
- Rails 8.1+
- SQLite 3.8.0+
- Linux (for L4D2 server and systemd support)
- Left4Dead 2 base installation at
/opt/l4d2/installation/ - Overlays in
/opt/l4d2/overlays/ fuse-overlayfsinstalled and available
Setup Instructions
1. Install Dependencies
bundle install
2. Database Setup
bin/rails db:migrate
3. Enable Systemd User Services (One-Time Setup)
Allow the steam user to run persistent systemd services:
loginctl enable-linger steam
Without this, user-level systemd services will stop when the user logs out.
4. Create Systemd Helper Scripts
Create /opt/l4d2/bin/ directory and add helper scripts for server lifecycle:
mkdir -p /opt/l4d2/bin
Create /opt/l4d2/bin/start-server (referenced by systemd units):
#!/bin/bash
SERVER_ID=$1
exec /opt/l4d2/servers/${SERVER_ID}/merged/srcds_run -norestart -pidfile /opt/l4d2/servers/${SERVER_ID}/pid -game left4dead2 -ip 0.0.0.0 -port $(grep "^Port:" /opt/l4d2/servers/${SERVER_ID}/server.cfg | cut -d' ' -f2) +hostname "Server" +map c1m1_hotel
Create /opt/l4d2/bin/stop-server:
#!/bin/bash
SERVER_ID=$1
# Systemd handles killing the process
exit 0
Make scripts executable:
chmod +x /opt/l4d2/bin/start-server /opt/l4d2/bin/stop-server
5. Configure Steam API Key (Optional for Development)
For Steam authentication in production, set the Steam API key:
export STEAM_API_KEY="your-steam-api-key"
In development, a test key is used by default.
6. Run the Application
Development:
bin/dev
This starts:
- Rails server on
http://localhost:3000 - Solid Queue job processor (in Puma)
- Asset pipeline watcher
Architecture
Domain Models
- User — Steam-authenticated users
- Overlay — Filesystem directories layered via overlayfs (system or custom)
- ServerTemplate — Reusable server configuration (overlays + config + params)
- Server — Active instance of a ServerTemplate
- ConfigOption — Key-value server configuration (server.cfg)
- StartupParam — Command-line arguments passed to srcds_run
- Activity — Audit log of user actions
Workflow
-
Create ServerTemplate
- Select overlays (in priority order)
- Define config options (key=value for server.cfg)
- Define startup parameters
-
Spawn Server from Template
- User specifies: server name, port, optional parameter overrides
- Rails generates
/opt/l4d2/servers/{id}/server.cfg - Mounts overlayfs stack using fuse-overlayfs
- Creates systemd user unit at
~/.config/systemd/user/left4dead2-{id}.service - Starts service via
systemctl --user start
-
Monitor Server
- StatusUpdateJob polls every 30 seconds
- Checks systemd status + RCON health
- Updates Server.status and last_health_check_at
-
Manage Server
- Start/Stop/Restart via UI buttons
- Stream live logs via ActionCable + journalctl
- Delete server (stops service, removes config, unmounts overlayfs)
Directory Structure
/opt/l4d2/
├── installation/ # L4D2 base game files (read-only lower layer)
├── overlays/ # Overlay directories
│ ├── system_overlay_1/ # System overlays (created by setup.sh)
│ ├── custom_{id}/ # Custom overlays (user-created)
├── servers/ # Active server instances
│ └── {server_id}/
│ ├── server.cfg # Generated config
│ ├── upper/ # Overlayfs writable layer
│ ├── work/ # Overlayfs work directory
│ ├── merged/ # Overlayfs mount point (active game filesystem)
│ └── pid # Process ID file
├── configs/ # (Legacy - configs now live in /opt/l4d2/servers/{id}/)
└── bin/
├── start-server # Systemd ExecStart script
└── stop-server # Systemd ExecStop script
Key Libraries
- L4dServer::ConfigGenerator — Renders server.cfg from ConfigOptions
- L4dServer::Launcher — Mounts overlayfs, generates systemd unit
- L4dServer::SystemdManager — Wraps systemctl commands
- L4dServer::HealthChecker — Monitors server health via systemd + RCON
Background Jobs
- SpawnServerJob — Executed when user spawns server (mounts, starts)
- StatusUpdateJob — Recurring job (every 30s) polling server status
WebSocket
- LogChannel — Streams
journalctloutput to connected clients in real-time
Development Notes
Running Tests
bin/rails test
Console Access
bin/rails console
Viewing Logs
journalctl --user -u left4dead2-{server_id}.service -f
Database Migrations
bin/rails db:migrate # Apply pending migrations
bin/rails db:rollback # Revert last migration
Configuration
Environment Variables
STEAM_API_KEY— Steam OpenID API key (optional, test key used in dev)RAILS_ENV— Rails environment (development/production)RAILS_MASTER_KEY— Encryption key for credentials (production only)
Rails Credentials
bin/rails credentials:edit
Store sensitive data here (encrypted in Git).
Deployment (Kamal)
Docker deployment via Kamal:
kamal deploy
See config/deploy.yml for Kamal configuration.
Important: Ensure RAILS_MASTER_KEY is set in production environment.
Known Limitations / Future Work
- Single-Machine Only — Servers must run on same machine as Rails app
- No Backup/Restore — Server state is ephemeral; no state snapshots yet
- Basic Health Checks — RCON health check is simple; doesn't parse game state
- Manual Setup Scripts —
setup.shand wrapper scripts must be installed manually - No API — Web UI only; no REST/GraphQL API yet
Contributing
Follow Rails conventions and the style guide:
bundle exec rubocop -a # Auto-fix style issues
bundle exec brakeman # Security scanning
bundle exec bundler-audit # Gem vulnerability check
License
[Add your license here]