WarpEngine is a standalone, mountable Rails engine that turns any Rails application into a retro software catalog: catalog models, a CI-pipeline-callable release updater, a public read-only JSON API and optional ActiveAdmin resources that plug into the host's existing admin. The Teletype Games public site is its reference host, but the engine carries no TTG-specific code — any Rails app can mount it.
| Repository | Role |
|---|---|
tools/warp_engine |
The product. Consume it from here as a git-sourced gem, or from the Forgejo rubygems registry on tagged releases (warp_engine-vX.Y.Z). Read-only mirror — do not push or open PRs here. |
tools/teletypegames |
Development home (monorepo); CI republishes the mirror on every change. |
The per-platform build-tools repos are the producer side of the updater contract: each one ships CI/CD templates (an example project Makefile, an example Woodpecker pipeline and, where needed, a builder image Dockerfile) that build the game, name the artifacts by WarpEngine's convention, upload them to the artifact drop area and call the /update endpoint with the matching platform parameter.
| Repository | Platform |
|---|---|
tools/tic80-tools |
TIC-80 |
tools/ebitengine-tools |
Ebitengine |
tools/love-tools |
LÖVE |
tools/c64-tools |
Commodore 64 |
tools/godot-tools |
Godot |
tools/bevy-tools |
Bevy |
tools/phaser-tools |
Phaser |
tic80pro-dockerfile |
TIC-80 Pro builder image (used by the TIC-80 pipeline) |
Software, Release, ReleaseAsset, ExternalLink, PlatformLink, Image, SoftwareImage, Download models with soft-delete semantics and download statistics.| Endpoint | Purpose |
|---|---|
GET /api/software |
Full catalog with releases, assets, links and download counts. |
GET /api/software/highlighted |
The currently highlighted title. |
GET /api/builds |
Global build matrix: expected asset kinds per platform. |
GET /api/softwares/:name/builds |
Actual vs. missing build assets per release. |
GET /api/image/:id |
Serves catalog images. |
GET /api/download?path= |
Serves a build artifact and logs a download record. |
GET /file/*path |
Serves static build output (web-playable games, docs). |
GET /update |
The updater — see below. Protected by a shared secret. |
Publishing a release from CI is two steps:
file_container_path, named by convention: <name>-<version>.metadata.json, <name>-<version>.html.zip, <name>-<version>-win-x64.zip, <name>-<version>.tic, etc. Each platform declares which asset kinds it expects — see GET /api/builds.GET /update?platform=<platform>&name=<name>&version=<version> with the secret in the X-Update-Secret header (preferred) or ?secret=.The updater extracts archives, parses metadata (JSON, or the Lua comment header for TIC-80), and upserts the Software, ExternalLink, Release and ReleaseAsset records in one transaction. Soft-deleted records are resurrected on re-ingest.
You don't have to build this pipeline from scratch: the platform build tooling repos provide ready-made Makefile and Woodpecker templates that implement both steps for every supported platform.
Security note: if no
update_secretis configured, the/updateendpoint rejects every request (it does not fall back to an empty secret).
# Gemfile — from git:
gem "warp_engine", git: "https://git.teletypegames.org/tools/warp_engine.git"
# ...or from the Forgejo rubygems registry (tagged releases):
source "https://git.teletypegames.org/api/packages/tools/rubygems" do
gem "warp_engine"
end
rails g warp_engine:install # initializer + create_warp_engine_tables migration
rails db:migrate
# config/routes.rb — keep it the last entry so the host's own routes win
mount WarpEngine::Engine => "/"
# config/initializers/warp_engine.rb
Rails.application.config.to_prepare do
WarpEngine.configure do |c|
c.file_container_path = ENV.fetch("FILE_CONTAINER_PATH", "/softwares")
c.image_container_path = ENV.fetch("IMAGE_CONTAINER_PATH", "/images")
c.update_secret = ENV["UPDATE_SECRET"]
# If host models reference catalog images, register them so the admin
# Images page counts them as "in use":
c.image_owners = [
{
label: "member",
image_ids: -> { Member.where.not(image_id: nil).distinct.pluck(:image_id) },
usage_label: ->(image) { "member" if Member.where(image_id: image.id).exists? }
}
]
end
end
The host owns the single ActiveAdmin instance — authentication (Devise), theme, assets and the /admin routes. WarpEngine only appends its resource files to ActiveAdmin.application.load_paths, so the engine's resources appear in the host's menu as if they were its own. Two things to copy into a new host:
/admin/files?picker=1&field=<dom_id>) in the host's active_admin.js;"#{WarpEngine::Engine.root}/app/controllers/**/*.rb" to its api_controllers_matcher.The one place where the catalog needs host knowledge — "which host models use catalog images?" — is inverted into the image_owners config hook shown above.
default_scope { where(deleted_at: nil) }).ActiveSupport.on_load(:warp_engine_<model>) hooks.The engine ships an RSpec suite running against a bundled dummy app (MySQL):
bundle install
bundle exec rake app:db:prepare RAILS_ENV=test
bundle exec rspec