Batocera and the ES-family distributions run on ARM as much as on x86_64 — Raspberry Pi, Odroid, the retro handhelds — and a linux_x64 binary installs there but will not start. There was no Linux ARM asset kind at all: KINDS had linux_x86 and linux_x64 and mac_arm64, but nothing for 64-bit ARM Linux. Registering the kind is deliberately separate from producing it: a platform service only includes BuildLinuxArm64 once its pipeline builds the artifact, otherwise /api/builds would report it missing for every release. Hence Ebitengine and Bevy only. Godot needs a Linux arm64 export preset in each game repo first; LÖVE fuses an upstream AppImage that ships x86_64 only; TIC-80's export command has no ARM target. Ebitengine needs cgo on Linux, so binary_build gained a cross-compiler argument — and unsets CC for native targets, otherwise a build after the ARM one silently picks up the cross gcc. Verified by cross-compiling both demos in the rebuilt images: each produced a genuine AArch64 ELF (e_machine 183), not a silent fallback. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Teletype Games
Monorepo for the Teletype Games portal: a Vue 3 frontend, a Rails 8 API host app, and the reusable WarpEngine software-catalog engine.
Project structure
apps/
frontend/ # Vue 3 + Vite + TypeScript + Tailwind SPA
api/ # Rails 8 host app: TTG-specific API + ActiveAdmin shell
libs/
ruby/warp_engine/ # WarpEngine: mountable Rails engine (catalog, updater, admin resources)
The API is split in two layers:
- WarpEngine (
libs/ruby/warp_engine) owns the software catalog: models (softwares, releases, release assets, images, platform links, download stats), the CI-callable/build/*publishing endpoints, the public read-only JSON API (/api/software*,/api/builds*,/api/image,/api/download,/file/*) and the catalog ActiveAdmin resources. See its README. - The host app (
apps/api) owns everything TTG-specific: members, events, wiki proxy, RSS feeds, Devise/ActiveAdmin authentication, theming and assets. It consumes WarpEngine as a path gem and mounts it at/.
Development environment
docker compose up -d
| Service | URL |
|---|---|
| Frontend | http://${WEBAPP_DOMAIN} |
| API | http://${WEBAPP_DOMAIN}/api |
| Admin | http://${WEBAPP_DOMAIN}/admin |
| API docs | http://${WEBAPP_DOMAIN}/api/swagger |
The api image is built from the repo root (so the libs/ path gems are visible
during bundle install) and mounts ./libs at runtime. After changing the
compose file or the Gemfile, rebuild with docker compose up -d --build api.
Testing
make api-test
Runs the host suite (apps/api, softwares_test DB), the WarpEngine suite
(dummy app, warp_engine_test DB) and a production-mode zeitwerk:check.
Individually:
docker exec -e RAILS_ENV=test api bundle exec rspec # host
docker exec -w /libs/ruby/warp_engine -e RAILS_ENV=test api bundle exec rspec # engine
JSON contract baselines for /api/software and /api/builds live in
apps/api/spec/snapshots/ — diff against them after refactors.
Linting
Backend (RuboCop)
docker compose run --rm --no-deps api bundle exec rubocop # check
docker compose run --rm --no-deps api bundle exec rubocop -A # autofix
Config: apps/api/.rubocop.yml (rubocop-rails-omakase preset)
Frontend (ESLint)
docker compose run --rm --no-deps frontend npm run lint # check
docker compose run --rm --no-deps frontend npm run lint:fix # autofix
Config: apps/frontend/eslint.config.js (ESLint 9 flat config, Vue + TypeScript)