Merge branch 'dev'

This commit is contained in:
Jeff Emmett 2026-02-27 17:52:07 -08:00
commit 6f4befdd19
2 changed files with 67 additions and 56 deletions

View File

@ -51,92 +51,93 @@ services:
- "traefik.http.routers.rspace-canvas.rule=HostRegexp(`{subdomain:[a-z0-9-]+}.rspace.online`) && !Host(`rspace.online`) && !Host(`www.rspace.online`) && !Host(`auth.rspace.online`)"
- "traefik.http.routers.rspace-canvas.entrypoints=web"
- "traefik.http.routers.rspace-canvas.priority=100"
# ── Standalone domain routing (priority 120) ──
- "traefik.http.routers.rspace-rbooks.rule=Host(`rbooks.online`)"
# ── Standalone domain routing (priority 120) — redirect to rspace.online ──
# Each rule matches bare domain + any subdomain (e.g. rnotes.online, alice.rnotes.online)
- "traefik.http.routers.rspace-rbooks.rule=Host(`rbooks.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rbooks.online`)"
- "traefik.http.routers.rspace-rbooks.entrypoints=web"
- "traefik.http.routers.rspace-rbooks.priority=120"
- "traefik.http.routers.rspace-rbooks.service=rspace-online"
- "traefik.http.routers.rspace-rpubs.rule=Host(`rpubs.online`)"
- "traefik.http.routers.rspace-rpubs.rule=Host(`rpubs.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rpubs.online`)"
- "traefik.http.routers.rspace-rpubs.entrypoints=web"
- "traefik.http.routers.rspace-rpubs.priority=120"
- "traefik.http.routers.rspace-rpubs.service=rspace-online"
- "traefik.http.routers.rspace-rchoices.rule=Host(`rchoices.online`)"
- "traefik.http.routers.rspace-rchoices.rule=Host(`rchoices.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rchoices.online`)"
- "traefik.http.routers.rspace-rchoices.entrypoints=web"
- "traefik.http.routers.rspace-rchoices.priority=120"
- "traefik.http.routers.rspace-rchoices.service=rspace-online"
- "traefik.http.routers.rspace-rfunds.rule=Host(`rfunds.online`)"
- "traefik.http.routers.rspace-rfunds.rule=Host(`rfunds.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rfunds.online`)"
- "traefik.http.routers.rspace-rfunds.entrypoints=web"
- "traefik.http.routers.rspace-rfunds.priority=120"
- "traefik.http.routers.rspace-rfunds.service=rspace-online"
- "traefik.http.routers.rspace-rforum.rule=Host(`rforum.online`)"
- "traefik.http.routers.rspace-rforum.rule=Host(`rforum.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rforum.online`)"
- "traefik.http.routers.rspace-rforum.entrypoints=web"
- "traefik.http.routers.rspace-rforum.priority=120"
- "traefik.http.routers.rspace-rforum.service=rspace-online"
- "traefik.http.routers.rspace-rvote.rule=Host(`rvote.online`)"
- "traefik.http.routers.rspace-rvote.rule=Host(`rvote.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rvote.online`)"
- "traefik.http.routers.rspace-rvote.entrypoints=web"
- "traefik.http.routers.rspace-rvote.priority=120"
- "traefik.http.routers.rspace-rvote.service=rspace-online"
- "traefik.http.routers.rspace-rwork.rule=Host(`rwork.online`)"
- "traefik.http.routers.rspace-rwork.rule=Host(`rwork.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rwork.online`)"
- "traefik.http.routers.rspace-rwork.entrypoints=web"
- "traefik.http.routers.rspace-rwork.priority=120"
- "traefik.http.routers.rspace-rwork.service=rspace-online"
- "traefik.http.routers.rspace-rcal.rule=Host(`rcal.online`)"
- "traefik.http.routers.rspace-rcal.rule=Host(`rcal.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rcal.online`)"
- "traefik.http.routers.rspace-rcal.entrypoints=web"
- "traefik.http.routers.rspace-rcal.priority=120"
- "traefik.http.routers.rspace-rcal.service=rspace-online"
- "traefik.http.routers.rspace-rtrips.rule=Host(`rtrips.online`)"
- "traefik.http.routers.rspace-rtrips.rule=Host(`rtrips.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rtrips.online`)"
- "traefik.http.routers.rspace-rtrips.entrypoints=web"
- "traefik.http.routers.rspace-rtrips.priority=120"
- "traefik.http.routers.rspace-rtrips.service=rspace-online"
- "traefik.http.routers.rspace-rwallet.rule=Host(`rwallet.online`)"
- "traefik.http.routers.rspace-rwallet.rule=Host(`rwallet.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rwallet.online`)"
- "traefik.http.routers.rspace-rwallet.entrypoints=web"
- "traefik.http.routers.rspace-rwallet.priority=120"
- "traefik.http.routers.rspace-rwallet.service=rspace-online"
- "traefik.http.routers.rspace-rdata.rule=Host(`rdata.online`)"
- "traefik.http.routers.rspace-rdata.rule=Host(`rdata.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rdata.online`)"
- "traefik.http.routers.rspace-rdata.entrypoints=web"
- "traefik.http.routers.rspace-rdata.priority=120"
- "traefik.http.routers.rspace-rdata.service=rspace-online"
- "traefik.http.routers.rspace-rnetwork.rule=Host(`rnetwork.online`)"
- "traefik.http.routers.rspace-rnetwork.rule=Host(`rnetwork.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rnetwork.online`)"
- "traefik.http.routers.rspace-rnetwork.entrypoints=web"
- "traefik.http.routers.rspace-rnetwork.priority=120"
- "traefik.http.routers.rspace-rnetwork.service=rspace-online"
- "traefik.http.routers.rspace-rtube.rule=Host(`rtube.online`)"
- "traefik.http.routers.rspace-rtube.rule=Host(`rtube.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rtube.online`)"
- "traefik.http.routers.rspace-rtube.entrypoints=web"
- "traefik.http.routers.rspace-rtube.priority=120"
- "traefik.http.routers.rspace-rtube.service=rspace-online"
- "traefik.http.routers.rspace-rmaps.rule=Host(`rmaps.online`)"
- "traefik.http.routers.rspace-rmaps.rule=Host(`rmaps.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rmaps.online`)"
- "traefik.http.routers.rspace-rmaps.entrypoints=web"
- "traefik.http.routers.rspace-rmaps.priority=120"
- "traefik.http.routers.rspace-rmaps.service=rspace-online"
- "traefik.http.routers.rspace-rnotes.rule=Host(`rnotes.online`)"
- "traefik.http.routers.rspace-rnotes.rule=Host(`rnotes.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rnotes.online`)"
- "traefik.http.routers.rspace-rnotes.entrypoints=web"
- "traefik.http.routers.rspace-rnotes.priority=120"
- "traefik.http.routers.rspace-rnotes.service=rspace-online"
- "traefik.http.routers.rspace-rfiles.rule=Host(`rfiles.online`)"
- "traefik.http.routers.rspace-rfiles.rule=Host(`rfiles.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rfiles.online`)"
- "traefik.http.routers.rspace-rfiles.entrypoints=web"
- "traefik.http.routers.rspace-rfiles.priority=120"
- "traefik.http.routers.rspace-rfiles.service=rspace-online"
- "traefik.http.routers.rspace-rphotos.rule=Host(`rphotos.online`)"
- "traefik.http.routers.rspace-rphotos.rule=Host(`rphotos.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rphotos.online`)"
- "traefik.http.routers.rspace-rphotos.entrypoints=web"
- "traefik.http.routers.rspace-rphotos.priority=120"
- "traefik.http.routers.rspace-rphotos.service=rspace-online"
- "traefik.http.routers.rspace-rinbox.rule=Host(`rinbox.online`)"
- "traefik.http.routers.rspace-rinbox.rule=Host(`rinbox.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rinbox.online`)"
- "traefik.http.routers.rspace-rinbox.entrypoints=web"
- "traefik.http.routers.rspace-rinbox.priority=120"
- "traefik.http.routers.rspace-rinbox.service=rspace-online"
- "traefik.http.routers.rspace-rcart.rule=Host(`rcart.online`)"
- "traefik.http.routers.rspace-rcart.rule=Host(`rcart.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rcart.online`)"
- "traefik.http.routers.rspace-rcart.entrypoints=web"
- "traefik.http.routers.rspace-rcart.priority=120"
- "traefik.http.routers.rspace-rcart.service=rspace-online"
- "traefik.http.routers.rspace-rsplat.rule=Host(`rsplat.online`)"
- "traefik.http.routers.rspace-rsplat.rule=Host(`rsplat.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rsplat.online`)"
- "traefik.http.routers.rspace-rsplat.entrypoints=web"
- "traefik.http.routers.rspace-rsplat.priority=120"
- "traefik.http.routers.rspace-rsplat.service=rspace-online"
- "traefik.http.routers.rspace-rswag.rule=Host(`rswag.online`)"
- "traefik.http.routers.rspace-rswag.rule=Host(`rswag.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rswag.online`)"
- "traefik.http.routers.rspace-rswag.entrypoints=web"
- "traefik.http.routers.rspace-rswag.priority=120"
- "traefik.http.routers.rspace-rswag.service=rspace-online"
- "traefik.http.routers.rspace-rsocials.rule=Host(`rsocials.online`)"
- "traefik.http.routers.rspace-rsocials.rule=Host(`rsocials.online`) || HostRegexp(`{sub:[a-z0-9-]+}.rsocials.online`)"
- "traefik.http.routers.rspace-rsocials.entrypoints=web"
- "traefik.http.routers.rspace-rsocials.priority=120"
- "traefik.http.routers.rspace-rsocials.service=rspace-online"

View File

@ -982,8 +982,22 @@ const server = Bun.serve<WSData>({
const hostClean = host?.split(":")[0] || "";
const subdomain = getSubdomain(host);
// ── Standalone domain → internal rewrite to module routes ──
const standaloneModuleId = domainToModule.get(hostClean);
// ── Standalone domain → 301 redirect to rspace.online ──
// Check both bare domain and subdomain variants (e.g. rnotes.online, alice.rnotes.online)
let standaloneModuleId = domainToModule.get(hostClean);
let standaloneSub: string | null = null;
if (!standaloneModuleId) {
// Check if this is a subdomain of a standalone domain (e.g. alice.rnotes.online)
const hostParts = hostClean.split(".");
if (hostParts.length >= 3) {
const baseDomain = hostParts.slice(-2).join(".");
const candidate = domainToModule.get(baseDomain);
if (candidate) {
standaloneModuleId = candidate;
standaloneSub = hostParts.slice(0, -2).join(".");
}
}
}
if (standaloneModuleId) {
// Self-fetch detection: landing proxy uses this User-Agent;
// return 404 to break circular fetch so the generic fallback is used
@ -991,41 +1005,37 @@ const server = Bun.serve<WSData>({
return new Response("Not found", { status: 404 });
}
// Static assets pass through
if (url.pathname !== "/" && !url.pathname.startsWith("/api/") && !url.pathname.startsWith("/ws/")) {
const assetPath = url.pathname.slice(1);
if (assetPath.includes(".")) {
const staticResponse = await serveStatic(assetPath);
if (staticResponse) return staticResponse;
}
}
// Root path → redirect to rspace.online/{moduleId} landing page
if (url.pathname === "/") {
return Response.redirect(`https://rspace.online/${standaloneModuleId}`, 302);
}
// Sub-paths: rewrite internally → /{space}/{moduleId}/...
// Determine the space and remaining path
const pathParts = url.pathname.split("/").filter(Boolean);
let space = "demo";
let suffix = "";
let space = standaloneSub || null; // subdomain on standalone domain = space
let remainingPath = "";
if (
pathParts.length > 0 &&
!pathParts[0].includes(".") &&
pathParts[0] !== "api" &&
pathParts[0] !== "ws"
) {
space = pathParts[0];
suffix = pathParts.length > 1 ? "/" + pathParts.slice(1).join("/") : "";
} else if (url.pathname !== "/") {
suffix = url.pathname;
if (pathParts.length > 0 && !pathParts[0].includes(".") &&
pathParts[0] !== "api" && pathParts[0] !== "ws") {
// First path segment is the space (if no subdomain already set it)
if (!space) {
space = pathParts[0];
remainingPath = pathParts.length > 1 ? "/" + pathParts.slice(1).join("/") : "";
} else {
remainingPath = url.pathname;
}
} else {
remainingPath = url.pathname === "/" ? "" : url.pathname;
}
const rewrittenPath = `/${space}/${standaloneModuleId}${suffix}`;
const rewrittenUrl = new URL(rewrittenPath + url.search, `http://localhost:${PORT}`);
const rewrittenReq = new Request(rewrittenUrl, req);
return app.fetch(rewrittenReq);
// Build redirect URL
let redirectUrl: string;
if (space) {
// Space-qualified: alice.rnotes.online/path or rnotes.online/alice/path
// → alice.rspace.online/rnotes/path
redirectUrl = `https://${space}.rspace.online/${standaloneModuleId}${remainingPath}`;
} else {
// No space: rnotes.online/ or rnotes.online/api/...
// → rspace.online/rnotes or rspace.online/rnotes/api/...
redirectUrl = `https://rspace.online/${standaloneModuleId}${remainingPath}`;
}
if (url.search) redirectUrl += url.search;
return Response.redirect(redirectUrl, 301);
}
// ── WebSocket upgrade ──