A WarpEngine mountolható Rails engine, amely bármelyik Rails-appból retró szoftverkatalógust csinál. Egy játékrepó egyetlen soros
.woodpecker.yamljelölőt visz, a motor kiszolgálja a teljes CI-pipeline-t, a buildek HTTP-n töltődnek fel, egy publikálási hívás pedig upserteli a katalógust. Publikus, read-only JSON API és opcionális ActiveAdmin resource-ok is járnak hozzá.
Software, Release, ReleaseAsset, ExternalLink, PlatformLink, SoftwareImage, Download. Mind soft delete-tel, download tracking-gel.WarpEngine::CI::Woodpecker néven, és ez a default; a :none kikapcsolja a CI-t, más szerver pedig a hoszt által adott objektum.POST /build/upload és POST /build/publish, shared secret-tel vagy adatbázisbeli application token-ekkel hitelesítve.Image modell, a fájljai, az /api/image/:id endpoint-ja és az admin oldala a hosztban él; az engine egy szoftvert egy képhez köt, és adapteren keresztül publikálja az URL-t. Ugyanaz a tábla ezután avatarokat vagy bármi mást is kiszolgálhat, ami a site-nak kell.rake warp_engine:builds:check szoftverenként és kiadásonként kiírja, mely assetek vannak meg, és melyeket tudná még a platform pipeline-ja leszállítani. Lásd: Mely assetek hiányoznak.Platformonként két lista, és a különbség számít. Az elfogadott assetek az, amit a frissítő befogad egy kiadás publikálásakor (WarpEngine::Platform#expected_kinds). A pipeline ezeket építi az, amit a Woodpecker-sablonok ténylegesen elő is állítanak. Ahol eltérnek, ott a maradék kézi feltöltés.
| Platform | Felirat | Elfogadott assetek | A pipeline ezeket építi |
|---|---|---|---|
godot |
Godot | html, win_x86, win_x64, linux_x64, mac_universal | mindet |
tic80 |
TIC-80 | cartridge, source, html, docs, win_x64, linux_x64, mac_x64 | mindet |
bevy |
Bevy | html, win_x64, linux_x64, linux_arm64 | mindet |
love |
LÖVE | html, win_x64, linux_x64, mac_universal | mindet |
ebitengine |
Ebitengine | html, win_x86, win_x64, linux_x64, linux_arm64, mac_x64, mac_arm64 | html, win_x86, win_x64, linux_x64, linux_arm64 |
phaser |
Phaser | html | mindet |
c64 |
C64 | cartridge | mindet |
Az Ebitengine az egyetlen hiányosság: egy macOS-bináris osxcrosst igényel, amit a linuxos builder image nem visz, így a mac_x64 és a mac_arm64 mindig csak kézzel érkezik.
Egy kiadás akkor teljes, ha minden olyan asset-típust visz, amit a platformja pipeline-ja elő tud állítani — és semmi nem kényszeríti ki. Elbukik egy buildlépés, egy platform új célt kap, egy címet laptopról publikálnak, és a katalógus hét assetből hárommal szolgál ki egy kiadást. A motor ellenőrzőlistát szállít hozzá:
$ bin/rails warp_engine:builds:check
WarpEngine release asset coverage — latest release per software
expected: the kinds a platform's updater registers · [ ] the CI builds it · [-] the CI does not, upload it by hand
rabbitroller ebitengine v1.1.1 3/7
[x] html
[x] win_x64
[x] linux_x64
[ ] win_x86
[ ] linux_arm64
[-] mac_x64
[-] mac_arm64
The CI can build these and the release does not have them:
rabbitroller v1.1.1 (ebitengine): win_x86, linux_arm64
12 softwares, 12 releases, 61/78 assets present, 11 missing from CI-built kinds
4 softwares are missing an asset the CI builds
2 assets are expected but this CI does not build them
| Jelölés | Jelentés |
|---|---|
[x] |
a kiadásnak megvan ez az assetje |
[ ] |
a platform elvárja, és a pipeline meg is építi — valódi hiány, és ez az a sor, amivel érdemes kezdeni valamit |
[-] |
elvárt, de ez a CI nem tudja megépíteni (lásd a fenti táblázatot) — kézi feltöltés, vagy semmi |
[+] |
a kiadás olyan assetet visz, amit a platform nem vár el |
A beállítások környezeti változók, így shellből komponálhatók:
| Változó | Hatás |
|---|---|
NAME=rabbitroller |
egyetlen szoftver |
PLATFORM=ebitengine |
egyetlen platform |
RELEASES=all |
minden kiadás, nem csak a szoftverenkénti legfrissebb |
ONLY=missing |
csak azok a kiadások, amelyekből hiányzik egy CI által épített asset |
STRICT=1 |
nem nulla kilépési kód, ha bármi hiányzik — ütemezett ellenőrzéshez |
A „legfrissebb" a legfrissebb nem dev- verzió — ugyanaz a szabály, amit a katalógus API is használ, amikor megnevezi egy cím legutóbbi kiadását. A számok is objektumok: a WarpEngine::AssetCoverage a #rows, #missing_rows és #totals metódusokra válaszol, a WarpEngine::AssetCoverageChecklist pedig kirendereli őket.
Mindkét hoszton elérhető: a nyilvános oldalon és a Teletype Orbitban.
| Végpont | Mire való |
|---|---|
GET /api/service |
Mi ez a telepítés: verzió, kapuz-e a katalógus, hogyan lehet belépni. |
GET /api/software |
Teljes katalógus kiadásokkal, assetekkel, linkekkel, letöltésszámokkal és egy access blokkal. |
GET /api/software/highlighted |
A kiemelt cím. |
GET /api/builds |
Platformonként elvárt asset-típusok. |
GET /api/softwares/:name/builds |
Kiadásonként a meglévő és a hiányzó buildassetek. |
GET /api/image/:id |
Képek. 0.6 óta a hoszt végpontja — a motor közli a címet, a hoszt szolgálja ki. |
GET /api/download?path= |
Kiszolgál egy artifactot, és naplózza a letöltést. |
GET /file/*path |
Statikus buildkimenet (böngészőben játszható játékok, dokumentáció). |
POST /api/auth/device |
Elindít egy eszközbelépést, visszaadja a kódpárt. 404, ha nincs kliensidentitás beállítva. |
POST /api/auth/device/token |
Lekérdezi egy eszközbelépés tokenjét. |
DELETE /api/auth/token |
Visszavonja a kérésen érkező bearer tokent — kilépés. |
Minden olvasó végpont elfogad egy opcionális Authorization: Bearer … fejlécet; egyik sem követeli meg. A token csak azt változtatja meg, amit a hozzáférési szabályzattól kérdezünk. Az anonim hívó támogatott hívó.
Minden válasz visz egy WarpEngine-Version fejlécet, amely az akció lefutása előtt kerül rá — így a hibaválaszokon is rajta van, és egy kliens plusz kérés nélkül tud a motorverzió szerint ágazni.
$ curl -sI https://teletypegames.org/api/software | grep -i warpengine
WarpEngine-Version: 0.5.0
A hosztalkalmazás saját végpontjai (például az áruház-nyilvántartás) nem viszik — azok nem részei a motornak.
| Végpont | Mire való |
|---|---|
POST /build/upload |
Egy artifact (multipart file, opcionális sha256). X-Update-Secret kell hozzá. |
POST /build/publish |
A feltöltött artifactok regisztrálása kiadásként. X-Update-Secret kell hozzá. |
POST /build/config |
Woodpecker-konfigurációs kiterjesztés; a pipeline-YAML-t szolgálja ki. Aláírás-ellenőrzött (RFC 9421, ed25519). |
GET /build/config?platform= |
A generált YAML nyilvános előnézete. |
Két seam került be a 0.5.0-ban, mindkettő a storage adapter mintájára: dokumentált contract, a régi viselkedéssel azonos default, és egyetlen config kulcs a cseréjéhez. Ha egyiket sem állítod be, semmi nem változik.
c.access_policy = MyStore::AccessPolicy.new # :open (alapérték) = a katalógus, ahogy volt
A szabályzat három kérdésre válaszol:
| Metódus | Kérdés |
|---|---|
visible_software_scope |
mely címeket listázza a GET /api/software |
access_for |
mit tudunk meg egy kliensként az egyes címekről |
authorize_download |
átadható-e ez az artifact |
Az authorize_download kérdést az /api/download és a /file/* is felteszi, mielőtt bármit kiszolgálna, így egy hosztnak már nem kell árnyékolnia ezeket az útvonalakat.
Minden katalógusbejegyzés visz egy access blokkot, a nyílt szabályzat alatt is — egy kliensnek soha nem kell találgatnia, hogy a katalógus hallgat-e, vagy a cím egyszerűen nincs kapuzva:
"access": { "gated": true, "entitled": false,
"price": { "amountCents": 1490, "currency": "EUR" },
"purchaseUrl": "https://shop.example/games/slug",
"webUrl": "https://shop.example/play/slug" }
A mezőnevek szándékosan általánosak, mert egy kliens több katalógust olvas.
Ha a policy exception-t dob, az elutasításnak számít: üres katalógus, megtagadott letöltés, logolva. Egy artifactot soha nem szabad azért kiszolgálni, mert a gatekeeper crashelt.
Egy desktop kliensnek nincs cookie jar-ja és nincs böngésző session-je, ezért az engine login form helyett a device authorization grant-et (RFC 8628) használja.
c.access_token_owner_class = "User" # nil (alapérték): sehol nincs belépés
c.identity_verification_url = "/devices" # a te oldalad, ahol valaki beírja a kódot
c.subject_resolver = ->(request) { request.env["warden"]&.user } # böngésző felismerése
POST-ol az /api/auth/device végpontra, és megmutatja a userCode értéket;WarpEngine::DeviceGrantService#approve(user_code:, subject:) metódust — a jóváhagyáshoz session és HTML kell, ami nem az engine dolga;A token egy WarpEngine::ApplicationToken a catalog scope-pal, így a publish és a kliens credential-ök egyetlen táblában is szétválaszthatók maradnak. A subject_resolver fedi le a másik esetet: egy böngésző session-t visz, nem bearer token-t, és enélkül egy gated áruház a saját bejelentkezett látogatóit utasítaná vissza.
Ha az access_token_owner_class nincs beállítva, az /api/auth/* 404-et ad, az /api/service pedig auth: null-t jelent, így a kliens nem kínál sign-in-t.
A kép nem katalógusfogalom — ugyanaz a képtár szolgálja ki a borítókat,
a képernyőképeket és az avatarokat —, ezért a 0.6.0-tól a motoré csak a kapcsolat
(SoftwareImage: sorrendezett, egy alapértelmezett), miközben a modell, a fájlok, a
végpont és az adminoldal a hoszté. A rails g warp_engine:install
kiír egy működő készletet (app/models/image.rb, app/controllers/api/images_controller.rb,
egy create_images migrációt), a motor pedig adapteren keresztül éri el:
c.image_class_name = "Image" # alapérték
c.image_adapter = MyLibrary.new # vagy cseréld le teljesen az adaptert
| Metódus | Mire használjuk |
|---|---|
model_name / model |
az az osztály, amelyhez a SoftwareImage#image tartozik |
url_for(image_id) |
az imageUrl és az images[].url a katalógus JSON-jában, valamint az adminelőnézetek |
select_options |
a képválasztó a szoftverűrlapon |
build_from_upload(upload) |
„új kép feltöltése" ugyanezen az űrlapon |
label_for(record), available?(record) |
az admin felirata és a „megvan-e a fájl" ellenőrzés |
Az alapértelmezett adapter az /api/image/<id> címet publikálja — azt, amit a kliensek
mindig is használtak —, ezért a generált útvonal pontosan ezt szolgálja ki. Az
images tábla nem költözött: a motor egyszerűen abbahagyta a létrehozását, és a
software_images.image_id továbbra is ugyanazokra a sorokra mutat.
Egy játékrepónak nincs saját pipeline-ja. A Woodpecker minden pipeline-indításkor POST-olja az egysoros jelölőt a /build/config végpontra, és visszakapja a teljes YAML-t.
.woodpecker.yaml fájljában csak a platform: van (és a name:, ha a szoftvernév eltér a repó nevétől).POST /build/config végpontra.lib/warp_engine/ci/woodpecker/platforms/<platform>/pipeline.yaml.erb sablont a beállított builder image-dzsel — a YAML-nyelvjárás a szolgáltatóé, tehát az adapterrel együtt él.Egy repó úgy lép ki ebből, hogy valódi pipeline-YAML-t commitol: a nem jelölő konfiguráció 204 No Content választ kap, és változatlanul fut.
# .woodpecker.yaml
platform: godot
name: mygame # opcionális, alapból a repó neve
// metadata.json — a TIC-80-on kívül minden platformon ugyanez
{
"name": "mygame",
"version": "1.0.0",
"title": "My Godot Game",
"author": "You",
"desc": "A cool game",
"license": "MIT"
}
| Platform | Forráselvárások | Mit épít a pipeline |
|---|---|---|
godot |
exportbeállítások: Web, Windows x86, Windows x64, Linux x64, Mac universal | godot --headless --export-release előbeállításonként |
bevy |
szabványos Cargo-projekt; az assets/ a natív binárisok mellé kerül becsomagolva |
WASM, linux-x64, win-x64 (mingw) |
love |
szabványos LÖVE-projekt | love.js webes build, Windows exe, Linux AppImage, univerzális macOS .app |
ebitengine |
szabványos Go-projekt | WASM, win x86/x64, linux x64, mac x64/arm64 |
phaser |
JS-fájlok a src/ alatt |
szintaxisellenőrzés, a Phaser-runtime letöltése, webes zip |
c64 |
main.asm |
ACME-fordítás .prg kazettává |
tic80 |
<name>.inc, amely felsorolja az inc/ alatti darabokat |
összefűzés, luacheck, minifikálás, LDoc, HTML + kazetta + natív binárisok |
A TIC-80-nak nem kell metadata.json — a metaadat a Lua-fejléc:
-- title: My TIC Game
-- name: mygame
-- author: You
-- desc: A TIC-80 game
-- version: 1.0.0
function TIC() end
| Kulcs | Kötelező | Leírás |
|---|---|---|
platform |
igen | godot, tic80, bevy, love, ebitengine, phaser, c64 |
name |
nem | Szoftvernév; alapból a repó neve |
curl "https://your-host/build/config?platform=godot"
Minden generált pipeline egyetlen Woodpecker-secretet vár, az application_token értéket, amelyet X-Update-Secret fejlécként küld a /build/upload és a /build/publish végpontokra.
A verziózás mindenhol ugyanaz: a main/master ágon a verzió a metadata.json fájlból jön (TIC-80 esetén a -- version: sorból); más ágakon előtagot kap, pl. dev-1.0.0-my-feature.
Csak ott aktív, ahol az adapternek van url és api_token értéke (WarpEngine.ci.configured?). Addig az adminoldalak elrejtik magukat.
Pipeline rekordokba, név alapján párosítja az egyes repókat egy Software rekorddal, és örökli annak platformját. A Woodpeckerből eltűnt repók deaktiválódnak. A linkek kézzel szerkeszthetők maradnak.application_token secretet a tulajdonos repóira (korlátozás nélküli tokenek esetén minden aktív repóra); a törlés eltávolítja; a Rotate létrehoz egy cserét, mindenhova kiosztja, és egy lépésben visszavonja a régit.A PAT-nak Woodpecker-példányadminhoz kell tartoznia — a repók listázása csak admin által hívható végpont, minden más 403 User not authorized választ ad. Vedd fel a felhasználót a WOODPECKER_ADMIN listába, majd lépj ki és vissza: az adminjelző a bejelentkezéskor íródik, a szerver újraindítása nem elég.
Az X-Update-Secret kétféle hitelesítő adat egyikét viszi, amit az application_token_source választ ki. A módok kizárják egymást.
| Mód | Hitelesítő adat |
|---|---|
:env (alapérték) |
a közös titok az update_secret értékben; ha nincs beállítva, minden kérés elutasításra kerül |
:database |
WarpEngine::ApplicationToken rekordok: tulajdonos (pl. AdminUser), jogosultságok (update a publikáláshoz, upload a feltöltéshez), opcionális lejárat. Az adminban jönnek létre; a nyílt token egyszer jelenik meg, csak a SHA256-lenyomata tárolódik. A last_used_at követi a használatot. |
Cutover warning: a
:databasemódra váltás azonnal érvényteleníti a shared secret-et. Előbb hozd létre a token-eket.
# Gemfile — gitből:
gem "warp_engine", git: "https://git.teletypegames.org/engines/warp_engine.git"
# ...vagy a Forgejo rubygems-regiszteréből (taggelt kiadások):
source "https://git.teletypegames.org/api/packages/engines/rubygems" do
gem "warp_engine"
end
rails g warp_engine:install # initializer, képtár, migrációk
rails db:migrate
# config/routes.rb
namespace :api do
get "image/:id", to: "images#show" # a képtár a tiéd (lásd lentebb)
end
# a mount maradjon az utolsó, hogy a hoszt saját útvonalai nyerjenek
mount WarpEngine::Engine => "/"
A generátor kiírja a config/initializers/warp_engine.rb fájlt:
Rails.application.config.to_prepare do
WarpEngine.configure do |c|
c.file_container_path = ENV.fetch("FILE_CONTAINER_PATH", "/softwares")
# The image library is the host's; "Image" is the adapter's default.
# c.image_class_name = "Media::Picture"
# c.image_adapter = MyImageLibrary.new
c.update_secret = ENV["UPDATE_SECRET"]
# c.application_token_source = :database
# c.application_token_owner_class = "AdminUser"
# One adapter carries the whole CI side: server, recipes, signing key.
c.ci_adapter = WarpEngine::CI::Woodpecker::Adapter.new(
url: ENV["WOODPECKER_URL"],
api_token: ENV["WOODPECKER_API_TOKEN"],
repo_owner: ENV["WOODPECKER_REPO_OWNER"],
public_key_url: "https://ci.example.org/api/signature/public-key",
update_server: "https://catalog.example.org",
platforms: {
"godot" => { builder: "registry.example/godot-builder:4.6" },
"tic80" => { builder: "registry.example/tic80-builder:1.0",
exporter: "registry.example/tic80pro:1.0" },
"bevy" => { builder: "registry.example/bevy-builder:1.0" },
"love" => { builder: "registry.example/love-builder:1.0" },
"ebitengine" => { builder: "registry.example/ebitengine-builder:1.0" },
"phaser" => { builder: "registry.example/phaser-builder:1.0" },
"c64" => { builder: "registry.example/c64-builder:1.0" }
}
)
# c.max_upload_size = 500 * 1024 * 1024
# c.enforce_software_ownership = true
end
end
| Kulcs | Alapérték | Leírás |
|---|---|---|
file_container_path |
ENV["FILE_CONTAINER_PATH"] vagy /softwares |
Hol tárolódnak a buildartifactok |
update_secret |
ENV["UPDATE_SECRET"] |
Közös titok a /build/* végpontokhoz |
application_token_source |
:env |
:env vagy :database, kizárólagosan |
application_token_owner_class |
nil |
Tulajdonosmodell a :database módhoz |
max_upload_size |
500 MB | Korlát a /build/upload végponthoz |
enforce_software_ownership |
false |
Adatbázistoken-tulajdonos szerinti elszigetelés |
ci_adapter |
:woodpecker |
Melyik CI-szerver építi a játékokat; a :none kikapcsolja a CI-t, vagy egy hosztobjektum |
image_class_name |
"Image" |
A képtár mögötti hosztmodell |
image_adapter |
nil |
Lecseréli az alapértelmezett hosztmodell-adaptert |
access_policy |
:open |
Hozzáférési szabályzat objektum |
access_token_owner_class |
nil |
Felhasználómodell az eszközbelépéshez; nil = nincs belépés |
Az ActiveAdmin-példány a hoszté (hitelesítés, téma, útvonalak); a WarpEngine hozzáfűzi az erőforrásait az ActiveAdmin.application.load_paths listához. Egy új hosztnak két dolog kell:
/admin/files?picker=1&field=<dom_id>) az active_admin.js fájljában;"#{WarpEngine::Engine.root}/app/controllers/**/*.rb" bejegyzés az api_controllers_matcher beállításban.default_scope { where(deleted_at: nil) }).ActiveSupport.on_load(:warp_engine_<model>).bundle install
bundle exec rake app:db:prepare RAILS_ENV=test
bundle exec rspec