---
title: 'Első lépések'
url: 'https://wiki.teletypegames.org/hu/engines/rubbs/getting-started'
markdown: 'https://wiki.teletypegames.org/hu/engines/rubbs/getting-started.md'
lang: hu
date: '2026-10-27'
description: 'A rubbs kézikönyvének első oldala: hogyan telepítsd a gemet, hogyan írj és indíts el egy első boardot, melyik réteget mire használd a háromból, mit csinál a BBS.configure egyes kulcsa, hogyan bánik a szerver a kapcsolatokkal (korlátok, idle timeout, logolás), és hogyan épül fel és megy ki Dockerbe…'
taxonomy:
  tag:
    - reference
---

# Első lépések

> A [rubbs](https://wiki.teletypegames.org/hu/engines/rubbs) kézikönyvének első oldala: hogyan telepítsd a gemet, hogyan írj és indíts el egy első boardot, melyik réteget mire használd a háromból, mit csinál a `BBS.configure` egyes kulcsa, hogyan bánik a szerver a kapcsolatokkal (korlátok, idle timeout, logolás), és hogyan épül fel és megy ki Dockerben egy valódi board. A rubbs 1.0.0 alapján.

# Telepítés

A gem neve `bbs`, és nincs fent a rubygems.org-on: a saját Git-szerverünkről jön. A `Gemfile`-ba:

```ruby
source 'https://rubygems.org'

gem 'bbs', git: 'https://git.teletypegames.org/engines/rubbs.git'
```

utána `bundle install`. Ruby 3.2 vagy újabb kell (a kapcsolat idle timeoutját az `IO#timeout=` állítja be, lásd [Idle timeoutok](#idle-timeoutok)). A futásidejű függőségek az `artii` (FIGlet bannerek) és az `sqlite3` (`BBS::SQLite` és `BBS::Store`); utóbbi natív kiterjesztést fordít, ezért a gépen vagy az image-ben C-fordító kell.

Minden kiadásnak van tagje, `vX.Y.Z` (`v1.0.0`), a verzió pedig a `bbs.gemspec`-ben áll. A Bundler a `bundle install` pillanatában `HEAD`-en lévő commitot rögzíti a `Gemfile.lock`-ban, a `bundle update bbs` pedig a legújabbra lép. Egy kiadáson a `tag: 'v1.0.0'` tart meg, egy adott commitra a `ref: 'COMMIT'` köt. Aki 0.x-ről jön, a repó `UPGRADING.md`-jében minden megszűnt vagy átnevezett API-t megtalál a helyettesítőjével együtt.

Minden betöltéséhez egy require elég:

```ruby
require 'bbs'
```

# Hello world

```ruby
# bbs.rb
require 'bbs'

BBS.configure do |c|
  c.flow = BBS::Flow.define do
    big_banner 'My BBS'
    ask :name, prompt: 'Your name'
    say 'Welcome!', style: :success
    pause 'Hanging up...', seconds: 2
  end
end

BBS.start
```

```text
$ bundle exec ruby bbs.rb
BBS listening on port 2323 — connect with: telnet localhost 2323
```

Egy másik terminálban:

```text
$ telnet localhost 2323

      __  ___         ____  ____ _____
     /  |/  /_  __   / __ )/ __ ) ___/
    / /|_/ / / / /  / __  / __  \__ \
   / /  / / /_/ /  / /_/ / /_/ /__/ /
  /_/  /_/\__, /  /_____/_____/____/
         /____/

  Your name: Zsolt

  Welcome!

  Hanging up...
```

Amikor a flow lépései elfogynak, a szerver bontja a vonalat. A szerver termináljában a `Ctrl-C` leállítja (`Server stopped.`).

Bármilyen telnetkliens jó: a `telnet` parancs (macOS-en `brew install telnet`), a SyncTERM, a NetRunner, a Qodem, a PuTTY telnet módban. Az egérhez és a teljes képernyős rétegekhez xterm-escape-eket értő terminál kell (SyncTERM, vagy `telnet` iTerm2-ben, Terminal.app-ban, egy linuxos terminálban). Egy sima `nc` nem elég: nem válaszol a telnet negotiationre, így a szerver nem tudja meg az ablak méretét.

# A három réteg

A rubbs háromféleképpen tud a hívóval beszélni. Ezek nem egy egész boardra szóló alternatívák: egy hívás általában többön is végigmegy, és mindig egy Flow-ban kezdődik.

| Réteg | Mi ez | Mire való | Kézikönyv |
|---|---|---|---|
| `BBS::Flow` | Sororientált, deklaratív párbeszéd: a lépések fentről lefelé futnak (`say`, `ask`, `menu`, `persist`, `call`, …), a szöveg görög, mint egy régi BBS-en. | A terminál-kérdés, a login, egy kérdőív, ragasztó a többi köré. Minden board ezzel indul. | [Flow](https://wiki.teletypegames.org/hu/engines/rubbs/flow) |
| `BBS::Application` | Widget framework: menubar, státuszsor, egymást átfedő window-k, űrlapok, dialógusok, egér, témák, doorok. | Synchronet-stílusú főfelület több window-val és funkcióval, ANSI-terminálra. | [Application](https://wiki.teletypegames.org/hu/engines/rubbs/application) |
| `BBS::Petscii::Screen` és `BBS::Selector` | Egy soralapú board képernyője: fordított címsor, bekezdések, RETURN-nel lapozott oldalak, számozott kurzoros menük, beviteli mezők és egy szerkesztő. Commodore 64-en 40×25, modern terminálon bármekkora. | Egy board C64-es arca, vagy egy egész board, ami soronként dolgozik. | [Commodore 64](https://wiki.teletypegames.org/hu/engines/rubbs/commodore) |

Ökölszabály: kezdj egy Flow-val. Ha a boardnak window-k, dialógusok, menubar vagy egér kell, a fő része egy Application, előtte egy Flow-val a terminál-kérdéshez vagy a loginhoz. A C64-es arc, vagy egy board, ami soronként egyszerűbb, egy `call` lépésből hajtott `Petscii::Screen`.

A három egymás mellett, egy boardban:

```ruby
require 'bbs'

NEWS = ['rubbs 1.0 is out', 'Feedia reads RSS on a C64', 'New door: Pixelia'].freeze

DESKTOP = BBS::Application.define do
  theme BBS::Theme.synchronet
  menubar do
    menu '&File' do
      item '&About' do BBS::Dialogs.message(self, 'Hello from rubbs!') end
      item 'E&xit'  do :halt end
    end
  end
  status_hint('F10', 'Quit') { :halt }
end

def read_news(session)
  screen = BBS::Petscii::Screen.new(session, cols: 80, rows: 24)
  item = screen.pick('the news', NEWS)
  screen.say(item, color: BBS::Petscii::Colors::YE) if item
  screen.pause
end

BBS.configure do |c|
  c.flow = BBS::Flow.define do
    ask :name, prompt: 'Your name'
    menu 'Where to?', loop: true do
      option('News (line by line)')   { call { |_ctx, runner| read_news(runner.session) } }
      option('Desktop (Application)') { app DESKTOP }
      option('Log off')               { exit_menu }
    end
    say 'Goodbye!'
  end
end

BBS.start
```

A rétegek mellett vannak építőelemek, amelyeket belőlük érsz el: a `BBS::Door` és a `BBS::Gateway` átadja a vonalat egy másik programnak vagy boardnak ([Doorok](https://wiki.teletypegames.org/hu/engines/rubbs/doors)), a `BBS::FakeOS` egy szimulált gép ([Doorok](https://wiki.teletypegames.org/hu/engines/rubbs/doors)), a `BBS::AI` egy karakter, akivel beszélgetni lehet ([AI](https://wiki.teletypegames.org/hu/engines/rubbs/ai)), a `BBS::Petscii` és a `BBS::Terminal` a Commodore 64-es hívót szolgálja ki ([Commodore](https://wiki.teletypegames.org/hu/engines/rubbs/commodore)), a `BBS::SQLite`, a `BBS::Store`, a `BBS::HTTP`, a `BBS::Cache`, a `BBS::RateLimit` és a `BBS::Wikipedia` pedig egy tartalmas board adat- és hálózati részei ([Adat és hálózat](https://wiki.teletypegames.org/hu/engines/rubbs/data-and-network)).

# Egy kapcsolat útja

```mermaid
flowchart TD
  A[BBS::Server elfogad egy TCP-kapcsolatot] --> B{"kevesebb hívó van, mint max_sessions, és erről a címről kevesebb, mint max_sessions_per_ip?"}
  B -- nem --> X["kiírja: 'Too many callers right now' vagy 'Too many calls from your address', és bont"]
  B -- igen --> C[új thread, socket timeout = idle_seconds vagy 900 s]
  C --> D[BBS::Session.new: véletlen session_id]
  D --> E[telnet negotiation: WILL SGA, WILL ECHO, DO SGA, DO NAWS]
  E --> F{config.mouse és config.mouse_on_connect?}
  F -- igen --> G[xterm egérjelentés be]
  F -- nem --> H
  G --> H[a FlowRunner futtatja a config.flow-t]
  H --> I[lépések: say, ask, menu, call, ...]
  I --> K[app: Application]
  I --> L[call: Petscii::Screen, doorok, al-flow-k]
  K --> I
  L --> I
  I --> M[elfogyott a lépés, :halt jött, vagy a hívó bontott]
  M --> N[egér ki, socket zárva, on_session_end meghívva]
```

Ugyanez szövegesen:

```text
TCPServer ─ accept ─ admit? ─ Thread ─ Session#run
                                        ├ negotiate (SGA, ECHO, NAWS)
                                        ├ enable_mouse (config.mouse && config.mouse_on_connect)
                                        └ FlowRunner(config.flow).run
                                             ├ app  ─▶ Application#run ─ vissza a következő lépésre
                                             └ call ─▶ a saját kódod (Petscii::Screen, doorok, al-flow-k, BBS::Terminal)
          ensure: disable_mouse, socket zárása, config.on_session_end.call(session)
```

Minden kapcsolat a saját threadjében fut, saját `Session`-nel és saját flow-contexttel (`ctx`); a definíciók (`Flow.define`, `Application.define`) közös, csak olvasott sablonok. Ami változtatható dolgot betöltéskor egy definícióba teszel (egy konstans Hash vagy Array), azon minden hívó osztozik.

# BBS.configure

```ruby
BBS.configure do |c|
  c.flow = MY_FLOW
end
```

A `BBS.configure` az egyetlen, process-szintű `BBS::Config` objektumot adja a blokknak; a `BBS.config` visszaadja (első használatkor létrehozza). A `BBS.start` előtt konfigurálj; a szerver és a sessionök élőben olvassák az értékeket, így egy később átírt kulcs a későbbi kapcsolatokra hat.

| Kulcs | Alapérték | Mit csinál |
|---|---|---|
| `flow` | `nil` | A `BBS::Flow`, amit minden kapcsolat futtat. Kötelező: nélküle minden session `BBS.config.flow is not set` hibával áll le (logolva, a hívót bontja). |
| `port` | `ENV['BBS_PORT']` vagy `2323` | A `BBS.start` TCP-portja. A környezeti változót a config objektum létrejöttekor olvassa, vagyis az első `BBS.configure` vagy `BBS.config` hívásnál. |
| `max_sessions` | `ENV['BBS_MAX_SESSIONS']` vagy `100` | Egyszerre hány kapcsolatot szolgál ki. Eggyel több `Too many callers right now, try again later.` üzenetet kap, a szerver bont, és egy `BBS busy` sort logol. A környezeti változót a `BBS_PORT`-hoz hasonlóan olvassa; a `BBS.configure`-ban megadott érték erősebb. |
| `max_sessions_per_ip` | `ENV['BBS_MAX_SESSIONS_PER_IP']` vagy `6` | Egy címről egyszerre hány kapcsolatot szolgál ki. Eggyel több `Too many calls from your address, try again later.` üzenetet kap, és a szerver bont (a logban `BBS crowded`). A privát, loopback és link-local címekre nincs korlát: egy webes proxy, például a bbs-web, a Docker-hálózatból sok hívót hoz. |
| `idle_seconds` | `nil` | Az idle-korlát másodpercben. Ez minden kapcsolat socketjének olvasási/írási timeoutja (`nil` esetén 900 s), és egy `Application` kemény idle-bontása ("Idle" üzenettel). Lásd [Idle timeoutok](#idle-timeoutok). |
| `mouse` | `false` | Használ-e a board xterm egérjelentést (SGR, gombok és húzás). Az Applicationök és a doorok csak akkor kapcsolják be-ki maguk körül, ha ez igaz. |
| `mouse_on_connect` | `true` | A `mouse` mellett: elinduljon-e a jelentés rögtön a negotiation után. Állítsd `false`-ra, ha a flow előbb kérdez valamit, pl. egy kétarcú board, ami azt kérdezi, modern vagy C64: az egér escape-szekvenciája egy Commodore-on szemét volna. Modern terminálnál ilyenkor a `BBS::Terminal.flow` kapcsolja be. |
| `screens_dir` | `nil` | A `screen` lépések ERB-fájljainak mappája azoknál a flow-knál, amelyek nem adnak meg sajátot (`Flow.define(screens_dir:)`). Lásd [Flow: screenek](https://wiki.teletypegames.org/hu/engines/rubbs/flow). |
| `on_session_end` | `nil` | Egy callable, amit minden kapcsolat vége után meghív a `BBS::Session`-nel, rendes vég és hiba esetén is. A kapcsolat threadjében fut; logoláshoz, egy "ki van bent" listához vagy a sessionhöz kötött állapot takarításához jó. |
| `theme` | `nil` | Minden olyan Application témája, amelynek nincs saját `theme` hívása (és az alap nélküli `theme do … end` blokk kiindulópontja); `nil` esetén `BBS::Theme.synchronet`. A 0.19.0 óta. Lásd [Rajzolás](https://wiki.teletypegames.org/hu/engines/rubbs/drawing). |
| `log_tag` | `ENV['BBS_LOG_TAG']` vagy `'bbs'` | Ezzel kezdődik minden `BBS.log`-sor: a board technikai neve (`feedia-bbs`). Lásd [Logolás](#logolas). |
| `ai_concurrency` | `2` | A processzben egyszerre hány `BBS::AI::Client`-kérés fut; a következő legfeljebb 5 s-ot vár, aztán `BBS::AI::Busy`-t kap. Lásd [AI](https://wiki.teletypegames.org/hu/engines/rubbs/ai). |
| `user_agent` | `'rubbs/VERSION (+https://teletypegames.org)'` | A `BBS::HTTP`-kérések `User-Agent`-je. Lásd [Adat és hálózat](https://wiki.teletypegames.org/hu/engines/rubbs/data-and-network). |

Egy teljes konfiguráció:

```ruby
require 'bbs'

WHO = {}
WHO_LOCK = Mutex.new

BBS.configure do |c|
  c.port             = 2323
  c.max_sessions     = 20
  c.log_tag          = 'harbor-bbs'
  c.idle_seconds     = 600
  c.mouse            = true
  c.mouse_on_connect = true
  c.screens_dir      = File.join(__dir__, 'screens')
  c.on_session_end   = lambda do |session|
    WHO_LOCK.synchronize { WHO.delete(session.session_id) }
    puts "caller #{session.session_id} left"
  end

  c.flow = BBS::Flow.define do
    call do |ctx, _runner|
      WHO_LOCK.synchronize { WHO[ctx[:session_id]] = Time.now }
      nil
    end
    say 'Hello!', style: :success
    pause 'See you.', seconds: 2
  end
end

BBS.start
```

# BBS.start és BBS::Server

```ruby
BBS.start                # a port a BBS.config.port-ból
BBS.start(port: 2424)    # felülírva
```

A `BBS.start(port: BBS.config.port)` létrehoz egy `BBS::Server`-t, és meghívja a `run`-ját, ami a process teljes élete alatt blokkol. Ugyanez kézzel:

```ruby
BBS::Server.new(port: 2424).run
```

|  |  |
|---|---|
| Cím | `0.0.0.0` (minden interfész), a megadott porton. Foglalt portnál induláskor `Errno::EADDRINUSE`-t dob. |
| Threadek | Kapcsolatonként egy Ruby-thread; az accept loop sosem vár egy sessionre. |
| Session-korlát | `config.max_sessions`, mutex alatt számolva; a hely a session threadjének végén szabadul fel. |
| Címenkénti korlát | `config.max_sessions_per_ip` a hívó címére, kivéve a privát, loopback és link-local címeket. |
| Accept-hibák | Az `accept` minden `SystemCallError` és `IOError` hibáját logolja (`accept: …`), 0,5 s-ot vár, és megy tovább. |
| Session vége | A hívó bontása, a broken pipe és az idle timeout `session: dropped by the client (…)` sorként kerül a logba; minden más kivétel `session: OSZTÁLY: ÜZENET` sorként, három sor backtrace-szel. Egyik sem állítja le a szervert. A hely akárhogy ér véget a session, felszabadul. |
| Leállás | Ctrl-C (`SIGINT`) vagy `SIGTERM` (`docker stop`): kiírja, hogy `Server stopped.`, és a `run` visszatér. |

## Idle timeoutok

Az `idle_seconds` két szinten hat:

1. **A socket.** Minden elfogadott socket `timeout = idle_seconds`-ot kap, vagy `BBS::Server::DEFAULT_TIMEOUT`-ot (900 s), ha az `nil`. Az ennél tovább váró blokkoló olvasás vagy írás kivételt dob, és a session "dropped by the client"-ként ér véget. Ez bontja azt a hívót, aki egy `ask` promptnál, egy "nyomj RETURN-t"-nél vagy egy doorban ül.
2. **Egy Application** egymásodperces tickkel olvassa a billentyűket, és számolja a csendes másodperceket; `idle_seconds`-nál lefuttatja az `on_idle` blokkját (ha van), és hacsak az nem `:keep`-et ad vissza, kiírja, hogy "Disconnected after N s idle.", és visszatér. `nil` esetén magától sosem bont. A `session.idle_seconds = 3600` egyetlen hívónál mindkettőt átállítja (egy sysopnál, egy hosszú olvasásnál).

## Logolás

A könyvtár minden logsort egyetlen metóduson, a `BBS.log(tag, message)`-en át ír a stderr-re, `LOG_TAG: TAG: ÜZENET` alakban, így sok board sorai egy logban is egyformán olvashatók. A `log_tag` legyen a board technikai neve, és a board maga is ezt a metódust használja:

```ruby
BBS.configure { |c| c.log_tag = 'feedia-bbs' }
BBS.log('catalog refresh', 'done in 3.2 s')   # feedia-bbs: catalog refresh: done in 3.2 s
BBS.log('catalog refresh', error)             # feedia-bbs: catalog refresh: SocketError: …
```

A hívóktól független munka (egy induláskor betöltött index, egy óránként frissülő katalógus) a `BBS.background(name, every: nil) { … }`-be kerül: egy threadbe, ami a hibát logolja, és egyre hosszabb szünet után újrapróbálja. Lásd [Adat és hálózat](https://wiki.teletypegames.org/hu/engines/rubbs/data-and-network).

# Egy board mappaszerkezete

A hálózatunk boardjai ([Feedia BBS](https://wiki.teletypegames.org/hu/services/feedia-bbs), [Alexandria BBS](https://wiki.teletypegames.org/hu/services/alexandria-bbs), a Teletype BBS) ugyanazt a szerkezetet követik: egy rövid `bbs.rb` köti össze a részeket, a board saját logikája a `lib/domain`-ben van (repositoryk, amik a külvilággal beszélnek, és szolgáltatások, amik eldöntik, mit lát a hívó), a board minden arca pedig a `lib/ui` egy-egy mappájában.

```text
feedia/
├── bbs.rb                 # configure + start, semmi más
├── Gemfile                # gem 'bbs', git: 'https://git.teletypegames.org/engines/rubbs.git'
├── Gemfile.lock
├── lib/
│   ├── domain/            # a board logikája, vonal nélkül tesztelhető
│   │   ├── feeds.rb
│   │   ├── inbox.rb
│   │   └── html_text.rb
│   └── ui/
│       ├── modern.rb      # UI::Modern.flow(...) → Flow + Application ANSI-terminálra
│       ├── petscii.rb     # UI::Petscii.flow(...) → Flow Commodore 64-re (PETSCII, 40 oszlop)
│       └── petscii/screen.rb
├── config/feeds.yml       # az image-dzsel szállított adat
├── data/                  # futásidejű adat (store-ok, logok), Dockerben volume
├── bin/                   # kis admin szkriptek
├── Dockerfile
├── compose.yaml
├── entrypoint.sh
└── env-example            # a környezeti változók, amiket a board olvas
```

A Feedia teljes `bbs.rb`-je:

```ruby
require 'bbs'
require_relative 'lib/domain/feeds'
require_relative 'lib/domain/inbox'
require_relative 'lib/ui/modern'
require_relative 'lib/ui/petscii'

feeds = Feeds.load(ENV.fetch('FEEDIA_FEEDS', File.join(__dir__, 'config/feeds.yml')))
inbox = Inbox.new(path: ENV.fetch('FEEDIA_INBOX', File.join(__dir__, 'data/inbox.jsonl')),
                  webhook: ENV.fetch('FEEDIA_DISCORD_WEBHOOK', ''))

BBS.configure do |config|
  config.mouse            = true
  config.mouse_on_connect = false
  config.idle_seconds     = 600

  config.flow = BBS::Terminal.flow(title: 'Feedia', modern: UI::Modern.flow(feeds, inbox),
                                   petscii: UI::Petscii.flow(feeds, inbox))
end

BBS.start
```

A részek:

- **A domain-objektumok egyszer jönnek létre** a `bbs.rb`-ben, és a UI-gyárak kapják meg őket (`UI::Modern.flow(feeds, inbox)`); a UI-kód Application `services`-ként vagy a closure-ön át éri el őket. A domain használhatja a rubbs adat- és hálózati részeit (`BBS::HTTP`, `BBS::Cache`, `BBS::SQLite`, `BBS.log`), a képernyőit soha. A repository dob, a szolgáltatás elkapja, egyszer logolja, és eldönti, mit lát a hívó; a UI sosem kap el `BBS::HTTP::Error`-t (lásd [Adat és hálózat](https://wiki.teletypegames.org/hu/engines/rubbs/data-and-network)).
- **Minden arc egy metódus, ami Flow-t ad vissza.** A `UI::Modern.flow` felépít egy `Application`-t, és egy kis Flow-t ad vissza, aminek a `call` lépése futtatja (`app.run(session: runner.session, session_id: ctx[:session_id], context: ctx)`), utána elköszön. A `UI::Petscii.flow` egy olyan Flow-t ad vissza, aminek a `call` lépése egy `BBS::Petscii::Screen`-t hajt.
- **A `BBS::Terminal.flow`** megkérdezi a hívót, milyen terminálja van, és a hozzá illő arcot futtatja; ezért `false` a `mouse_on_connect`. Egy egyarcú board a `config.flow`-ba közvetlenül a saját Flow-ját teszi. Lásd [Commodore](https://wiki.teletypegames.org/hu/engines/rubbs/commodore).

Az [Alexandria BBS](https://wiki.teletypegames.org/hu/services/alexandria-bbs) ugyanilyen, egy domain-objektummal (`lib/domain/wikipedia.rb`) és egy külön window-modullal (`lib/ui/modern/pharos.rb`). Az Impostor BBS a másik gyakori forma: egy gem, aminek a `lib/impostor.rb`-je egy saját `screens/` mappás Flow-t definiál, így ugyanaz a Flow önálló boardként és egy másik board doorjaként is fut (lásd [Flow](https://wiki.teletypegames.org/hu/engines/rubbs/flow)).

# Docker

A boardok Dockerben futnak, mindegyik a saját konténerében, a hivatalos Ruby Alpine image-ből. A `git` a Gitből jövő gemhez kell, a `build-base` a natív kiterjesztésekhez (`sqlite3`).

`Dockerfile`:

```dockerfile
FROM ruby:3.3-alpine

RUN apk add --no-cache git build-base tzdata

WORKDIR /app

ENV BUNDLE_WITHOUT=development

COPY Gemfile Gemfile.lock ./
RUN bundle install

COPY bbs.rb entrypoint.sh ./
COPY lib/ ./lib/

RUN chmod +x entrypoint.sh

EXPOSE 2323

ENTRYPOINT ["./entrypoint.sh"]
```

`entrypoint.sh`: a boardjaink minden induláskor frissítik a rubbs-t, így egy újraindítás rebuild nélkül is felveszi az új kiadást. Szerver-újraindítás után a Git-szerver egy ideig 502-t adhat, ezért legfeljebb egy percig vár, és ha kell, a telepített verzióval indul:

```sh
#!/bin/sh
set -e
RUBBS_GIT=https://git.teletypegames.org/engines/rubbs.git
tries=0
until git ls-remote "$RUBBS_GIT" HEAD >/dev/null 2>&1 || [ "$tries" -ge 12 ]; do
  tries=$((tries + 1))
  sleep 5
done
bundle update bbs || echo "rubbs update failed, starting with the installed version" >&2
exec bundle exec ruby bbs.rb
```

Az `exec` számít: így a Ruby lesz a PID 1, és a `docker stop` jelzése eljut hozzá.

`compose.yaml`:

```yaml
services:
  myboard:
    build: .
    container_name: myboard
    mem_limit: 128m
    ports:
      - "2323:2323"
    volumes:
      - ./data:/app/data
    environment:
      - TZ=Europe/Budapest
    restart: unless-stopped
```

A konténerben a board mindig a 2323-on figyel; a `ports` hoszt oldala adja a nyilvános portot (a Feediáé `"2327:2323"`). Amit a board futás közben ír (`BBS::Store`- és `BBS::SQLite`-fájlok), az a `/app/data` alá kerül, ami volume, így túléli a rebuildet.

```text
docker compose up -d --build
docker compose logs -f myboard
telnet localhost 2323
```

A board kimenete (`puts`, `BBS.log`) a konténer logjába megy: a figyelő sor, a bontott sessionök és a session-hibák backtrace-szel, minden sor a board `log_tag`-je után.

# Hogyan tovább

- A párbeszéd megírása: [Flow](https://wiki.teletypegames.org/hu/engines/rubbs/flow).
- Adat, HTTP, cache és korlátok: [Adat és hálózat](https://wiki.teletypegames.org/hu/engines/rubbs/data-and-network).
- Window-s főfelület: [Application](https://wiki.teletypegames.org/hu/engines/rubbs/application), [Widgetek](https://wiki.teletypegames.org/hu/engines/rubbs/widgets), [Window-k](https://wiki.teletypegames.org/hu/engines/rubbs/windows).
- Commodore 64-es arc: [Commodore](https://wiki.teletypegames.org/hu/engines/rubbs/commodore).

# Lásd még

- [rubbs](https://wiki.teletypegames.org/hu/engines/rubbs) — a főoldal
- [Flow](https://wiki.teletypegames.org/hu/engines/rubbs/flow) — a sororientált párbeszéd-réteg
- [Application](https://wiki.teletypegames.org/hu/engines/rubbs/application) — a widget framework
- [Commodore 64](https://wiki.teletypegames.org/hu/engines/rubbs/commodore) — PETSCII, `BBS::Petscii::Screen`, `BBS::Selector`
- [Adat és hálózat](https://wiki.teletypegames.org/hu/engines/rubbs/data-and-network) — `BBS::SQLite`, `BBS::Store`, `BBS::HTTP`, `BBS.log`, `BBS.background`
- [Telnet és Store](https://wiki.teletypegames.org/hu/engines/rubbs/telnet-and-store) — a session, a billentyűk, az egér
- [Változásnapló](https://wiki.teletypegames.org/hu/engines/rubbs/changelog)

'; 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: [RuBBS](https://wiki.teletypegames.org/hu/engines/rubbs.md)
- Previous: [Flow](https://wiki.teletypegames.org/hu/engines/rubbs/flow.md)
- Next: [Minimális projekt](https://wiki.teletypegames.org/hu/engines/rubbs/minimal-project.md)
