A small, self-hostable proxy that turns a Git host reference into an installable
Roblox model — with no central registry. Packages aren't published or
namespaced here; they live in whatever GitHub or Codeberg repo their author
chose, and the proxy resolves owner:repo[@version] on demand. Point the
Cealshell CLI at the official instance or run your own.
It is stateless apart from a tiny version cache: it resolves
owner:repo[@version] against a source provider (GitHub or Codeberg), fetches
the release's .rbxm/.rbxmx asset (or one committed via cealshell.toml), parses it
into the instance tree the plugin consumes, and returns it. No accounts, no
uploads, no download tracking.
This is the open-source proxy slice of Cealshell. User accounts, OAuth, admin and moderation tooling live in the private backend and are intentionally not included here.
| Route | Description |
|---|---|
GET /github/:ref |
Resolve :ref through GitHub |
GET /codeberg/:ref |
Resolve :ref through Codeberg (Forgejo/Gitea) |
GET /wally/:ref |
Resolve :ref through the Wally registry |
GET /pesde/:ref |
Resolve :ref through the pesde registry |
GET /:ref |
Default — resolves owner:repo[@version] through GitHub |
GET /health |
Liveness check |
:ref is owner:repo[@version], e.g. janisfox:marble or
janisfox:marble@1.2.0. Omitting the version resolves to the newest published
one. (owner/repo is also accepted.)
For the registry routes :ref is scope:name[@range], and the version part
accepts a full semver range — wally/sleitnick:component@^2 or
pesde/jiwonz:signal@>=0.1.0, <0.2.0. A range that matches no published version
is a 404 rather than a silent downgrade.
The response envelope is:
{
"ok": true,
"data": {
"package": { "provider": "github", "owner": "...", "repo": "...", "slug": "...", "version": "1.2.0" },
"instances": [ /* parsed .rbxm instance tree */ ],
"release_notes": "..."
}
}Registry responses use the same envelope and additionally carry a
dependencies array listing everything the graph walk pulled in.
Source providers serve a repository's prebuilt .rbxm. Registries instead
publish source archives plus a manifest of dependencies, so those routes do
more work:
- Resolve the requested range against the registry's published versions.
- Walk the dependency graph transitively, picking the highest version that satisfies each range and sharing one copy per package where ranges allow.
- Convert every archive to instances using Rojo's file conventions
(
init.luaucollapses into its folder,*.server.luaubecomes aScript, and a committeddefault.project.jsonwins over all of it). - Link them together in a wally-style
_Indextree so each package's ownrequirecalls resolve unchanged inside Studio.
Both registries' layouts are reproduced faithfully, because published code is
compiled against them: Wally packages find dependencies as siblings
(require(script.Parent.Promise)), pesde packages in a roblox_packages folder
(require(script.Parent.Parent.roblox_packages.go)).
Only Roblox-targeted pesde packages are installable; a package published solely for Lune or Luau is reported as not found. Nothing is written to the version cache for registry routes — the registry's own version list is authoritative.
Relevant environment variables (all optional):
| Variable | Default | Purpose |
|---|---|---|
WALLY_REGISTRY_URL |
https://api.wally.run |
Point at a private Wally registry |
WALLY_CLIENT_VERSION |
0.3.2 |
Sent as Wally-Version; the registry rejects downloads without it |
PESDE_REGISTRY_URL |
https://registry.pesde.dev |
Point at a private pesde registry |
To use your instance from the CLI, set it as the remote:
cshl remote set https://proxy.example.com
…or use a one-off override on a single reference, e.g.
proxy.example.com/janisfox:marble.
Either of:
- Release asset — attach a
.rbxmor.rbxmxto a GitHub/Codeberg release. The proxy serves the newest release that ships one, preferring.rbxmwhen a release carries both. cealshell.toml— commit a manifest pointing at a.rbxmin the repo, no release needed. See docs/manifest.md. When present it takes precedence over releases.
Requires Node 18+ and PostgreSQL.
cp .env.example .env # fill in DB_* (and optionally GITHUB_TOKEN)
npm install
npm run migrate # create the cache tables
npm run dev # or: npm run build && npm startA GITHUB_TOKEN / CODEBERG_TOKEN is optional but recommended — without one
you share the host's low unauthenticated API rate limit.
Source providers live in src/providers/source/. Implement the SourceProvider
interface (types.ts) — fetchVersions and fetchManifest — and register it in
index.ts. It auto-mounts at /<id>/:ref. GitHub and Codeberg are the built-in
examples; the contract assumes a release-with-attached-asset model.
src/
index.ts Express app — health + proxy routes only
routes/registry.ts The proxy HTTP handlers
services/
registry.ts Ref parsing, package upsert, version sync
rbxm.ts Fetch + parse a .rbxm into the instance tree
manifest.ts cealshell.toml reader
providers/source/ GitHub + Codeberg source providers
db/ pg connection, schema, migrate runner