---
title: 'Commodex BBS'
url: 'https://wiki.teletypegames.org/services/commodex-bbs'
markdown: 'https://wiki.teletypegames.org/services/commodex-bbs.md'
lang: en
date: '2026-11-01'
description: 'The Commodore 64 game compendium of the BBS network: search nearly 2,900 titles, see who made them and when, read about them, and trade cheats and tips with the other callers. Live at teletypegames.org:2331, in the browser on bbsweb, and through TTG Gate BBS Overview A Ruby app on the rubbs gem, …'
taxonomy:
  tag:
    - howto
---

# Commodex BBS

> The Commodore 64 game compendium of the BBS network: search nearly 2,900 titles, see who made them and when, read about them, and trade cheats and tips with the other callers.
> 
> **Live at `teletypegames.org:2331`**, in the browser on [bbsweb](https://bbsweb.teletypegames.org), and through [TTG Gate BBS](https://wiki.teletypegames.org/services/ttg-gate-bbs)

# Overview

A Ruby app on the [**rubbs**](https://git.teletypegames.org/engines/rubbs) gem, like the [Teletype BBS](https://wiki.teletypegames.org/services/teletype-bbs), [Alexandria BBS](https://wiki.teletypegames.org/services/alexandria-bbs) and [WarpEngine BBS](https://wiki.teletypegames.org/services/warp-engine-bbs).

It has two faces on one port:

- **Modern**: ANSI colours, windows, menus and the mouse (SyncTERM, NetRunner, PuTTY, bbsweb). The search in a dialog, lists of games with the card of the picked one on the right, the Wikipedia lead in a scrolling window, the tips, and a form for a new tip.
- **Commodore 64**: PETSCII on a 40×25 screen, for a C64 terminal program (CCGMS, Novaterm, UltimateTerm) behind a WiFi modem. A one-key menu, numbered lists, a page a game, a line for the search and an editor line for a tip.

A call starts with rubbs' `BBS::Terminal` question: RETURN takes the guess, `1` picks the modern face, `2` the C64.

# Where the data comes from

| What | Source | Licence |
|---|---|---|
| The catalog: title, year, publishers, developers, designers, composers, genres, game modes, series | [Wikidata](https://www.wikidata.org): every video game on the platform Commodore 64 (P400 = Q99775) | CC0 |
| About a game | the lead of its English Wikipedia article, fetched when asked | CC BY-SA 4.0, credited on the screen |
| Cheats and tips | the callers, checked by an admin before they show | the callers' own |

A snapshot of the catalog ships with the board (`config/catalog.json.gz`); the running board reads Wikidata again once a week and keeps the new copy in `data/`. A failed refresh keeps the old snapshot. The year is the first publication date given for the C64 where Wikidata has one, else the earliest date at all, so a few arcade conversions show their arcade year.

GameBase64 is **not** used: its licence forbids putting the GB64 data on a public board without the GB64 Team's written permission. If they give it, a GB64 importer can join the Wikidata catalog.

# Using it

**On a modern terminal:**

| Key | Action |
|---|---|
| F2 | Search a title |
| F3 | A random game |
| Games menu | By year, By genre, By company, With tips |
| Enter or Alt-B | The game's Wikipedia lead |
| Alt-T | The game's tips |
| Alt-D | Add a tip (Alt-S sends it) |
| F10 | The menu |
| Alt-X | Log off |

**On a Commodore 64** the main menu is `S` search, `Y` by year, `G` by genre, `C` by company, `R` a random game, `T` games with tips, `I` about the board, `Q` log off. A game's page has `W` Wikipedia, `T` tips, `A` add a tip, `Q` back.

A new tip waits for an admin; until then only its sender sees the thanks. A call may send three tips, one every 30 seconds, and the board takes at most 30 an hour.

# Configuration and data

| Variable | Default |  |
|---|---|---|
| `COMMODEX_BBS_CATALOG_SEED` | `config/catalog.json.gz` | the snapshot shipped with the image |
| `COMMODEX_BBS_CATALOG` | `data/catalog.json.gz` | the snapshot a refresh writes |
| `COMMODEX_BBS_CATALOG_TTL` | `604800` | seconds before Wikidata is read again; `0` never |
| `COMMODEX_BBS_WIKIPEDIA_CACHE` | `200` | Wikipedia leads kept in memory |
| `COMMODEX_BBS_TIPS_DB` | `data/tips.sqlite3` | the tips |
| `COMMODEX_BBS_TIPS_SEED` | `config/tips.yml` | tips shipped with the board, approved from the start |
| `COMMODEX_BBS_DISCORD_WEBHOOK` | empty (off) | rung for every new tip; a secret, in `.env` on the server |

The admins moderate the tips on the server:

```sh
docker exec commodex-bbs bin/tips list           # the pending tips
docker exec commodex-bbs bin/tips approved       # the approved ones
docker exec commodex-bbs bin/tips approve ID     # shown from now on
docker exec commodex-bbs bin/tips reject ID      # never shown
```

`bin/refresh-catalog` writes a new seed from Wikidata in a dev checkout.

# Under the hood

A push to master deploys it from Woodpecker (RuboCop, compose check, `ci-deploy commodex-bbs`); the container is `commodex-bbs`, in `/srv/stacks/commodex-bbs`, and `data/` stays between deploys. The entrypoint updates rubbs on start.

The code follows the house layering: `lib/domain` (`GameModel`, `TipModel`, `SummaryModel`; the catalog, Wikidata, Wikipedia and tip repositories; `CatalogService`, `AboutService`, `TipService`; the composition root `CommodexBBS`) knows nothing of telnet, and `lib/ui` holds `UI::Modern` and `UI::Petscii`. The catalog lives in memory (about 30 MB for the whole board), the tips in SQLite, so `bin/tips` and the board see the same data.

'; var prevLevel = 2; for (var i = 0; i < headings.length; i++) { var h = headings\[i\]; var text = h.textContent.trim(); var slug = text.toLowerCase() .replace(/\[áàâä\]/g,'a').replace(/\[éèêë\]/g,'e').replace(/\[íìîï\]/g,'i') .replace(/\[óòôöő\]/g,'o').replace(/\[úùûüű\]/g,'u') .replace(/\[^a-z0-9\]+/g, '-').replace(/^-|-$/g, ''); if (!slug) slug = 'heading-' + i; if (used\[slug\]) { slug += '-' + i; } used\[slug\] = true; h.id = slug; var level = parseInt(h.tagName.charAt(1)); var cls = level > 2 ? ' class="toc-child"' : ''; html += '- [' + text + '](#'%20+%20slug%20+%20')
'; } html += '
'; toc.innerHTML = html; // Scroll-spy: highlight current heading var links = toc.querySelectorAll('a'); var ids = \[\]; for (var j = 0; j < links.length; j++) ids.push(links\[j\].getAttribute('href').slice(1)); function updateActive() { var current = ''; for (var k = 0; k < ids.length; k++) { var el = document.getElementById(ids\[k\]); if (el && el.getBoundingClientRect().top <= 80) current = ids\[k\]; } for (var m = 0; m < links.length; m++) { links\[m\].parentNode.classList.toggle('active', links\[m\].getAttribute('href') === '#' + current); } } window.addEventListener('scroll', updateActive, { passive: true }); updateActive(); })();

---

## Navigation

- Parent: [Services](https://wiki.teletypegames.org/services.md)
- Previous: [BBS Web](https://wiki.teletypegames.org/services/bbsweb.md)
- Next: [Discord bots](https://wiki.teletypegames.org/services/discord-bots.md)
