In ACCESS=public mode the guard refused a gated bundle's source and its /__leaf/ pages, but not /__plugin-gallery/registry.json. That file is the raw discovery list for the smoke tools, and it still names key-cities, its tag and its two profiles to any visitor. No data, but a gated plugin must not reach a public surface. The Studio reads only manifest.json, which already leaves gated bundles out, so the registry is now refused in public mode as well. Dev mode is unchanged, and the smoke tools that read the registry run against a dev server. Measured with the cloud config run locally: - before: public mode served registry.json (200) naming key-cities; - after: public mode answers 404, including with a query string and a percent-encoded name; - manifest.json and / still answer 200 in public mode, and dev mode still serves registry.json.
91 lines
4.5 KiB
JavaScript
91 lines
4.5 KiB
JavaScript
// Overlay for the cloud dev server: the repo's own vite.config.js plus what a shared, public
|
|
// host needs. Every value comes from the environment (see deployment/cloud/README.md).
|
|
import base from '/app/repo/vite.config.js'
|
|
import { mergeConfig } from 'vite'
|
|
import { readFileSync } from 'node:fs'
|
|
import { GATED_PROFILES } from '/app/repo/internal/_tree-name.mjs'
|
|
|
|
const env = process.env
|
|
const SETUP_STATE = '/tmp/rudder-setup.state' // written by entry.sh: running -> done | failed
|
|
const setupState = () => { try { return readFileSync(SETUP_STATE, 'utf8').trim() } catch { return '' } }
|
|
|
|
// Rudder's setup runs in the background (entry.sh), so the site serves before the engine exists. While it
|
|
// runs, /api/rudder answers "starting" (the widget shows "Loading speech model…"; /events is held open so it
|
|
// does not retry in a loop). When setup ends, the dev server restarts ONCE: the repo's backend plugin only
|
|
// looks for the engine binary at start, and the restart is what starts it. 'pre' puts this ahead of that
|
|
// plugin's own "engine offline" answer.
|
|
const rudderStarting = {
|
|
name: 'cloud-rudder-starting',
|
|
enforce: 'pre',
|
|
apply: 'serve',
|
|
configureServer(server) {
|
|
if (setupState() !== 'running') return
|
|
server.middlewares.use('/api/rudder', (req, res, next) => {
|
|
if (setupState() !== 'running') return next()
|
|
const status = { engine: 'starting', error: '', audio: 'browser' }
|
|
if ((req.url || '').startsWith('/events')) {
|
|
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive' })
|
|
res.write(`data: ${JSON.stringify({ type: 'status', status })}\n\n`)
|
|
return
|
|
}
|
|
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
res.end(JSON.stringify(status))
|
|
})
|
|
const poll = setInterval(() => {
|
|
if (setupState() === 'running') return
|
|
clearInterval(poll)
|
|
server.config.logger.info(`[rudder] setup ${setupState() || 'ended'}: restarting the dev server to start the engine`)
|
|
server.restart()
|
|
}, 2000)
|
|
server.httpServer?.once('close', () => clearInterval(poll))
|
|
},
|
|
}
|
|
|
|
// ACCESS=public (entry.sh sets DX_PUBLIC_STUDIO=1): Vite owns the public port with no sign-in in front of it.
|
|
// - The backend's paths go to admin.mjs, whose gates decide: /cdn/ and /api-prod/api/ public, /pb/ records
|
|
// by PocketBase's own rules, and /pb/_/, _superusers and /admin/ for the admin password.
|
|
// - GATED bundles (gatedProfiles, e.g. key-cities) must never reach a public surface. The Studio already leaves
|
|
// them out of its list; here their source and dev pages are refused too, so a typed URL can't load them. So is
|
|
// /__plugin-gallery/registry.json, the raw discovery list for smoke tools: it still names the gated bundles and
|
|
// their profiles, and the Studio itself reads only manifest.json.
|
|
// - /__open-in-editor (Vite's own) would start an editor process on this server for a visitor: refused.
|
|
const PUBLIC = env.DX_PUBLIC_STUDIO === '1'
|
|
const publicGuard = {
|
|
name: 'cloud-public-guard',
|
|
enforce: 'pre',
|
|
apply: 'serve',
|
|
configureServer(server) {
|
|
if (!PUBLIC) return
|
|
server.middlewares.use((req, res, next) => {
|
|
let p = (req.url || '').split('?')[0]
|
|
try { p = decodeURIComponent(p) } catch { /* keep it raw */ }
|
|
const gated = GATED_PROFILES.some((g) => p.includes(`/plugin-designs/${g}/`) || p === `/__leaf/${g}` || p.startsWith(`/__leaf/${g}/`))
|
|
if (!gated && !p.startsWith('/__open-in-editor') && p !== '/__plugin-gallery/registry.json') return next()
|
|
res.statusCode = 404
|
|
res.setHeader('content-type', 'text/plain; charset=utf-8')
|
|
res.end('not found\n')
|
|
})
|
|
},
|
|
}
|
|
|
|
export default async (e) => {
|
|
const b = typeof base === 'function' ? await base(e) : base
|
|
const hmr = env.HMR_CLIENT_PORT
|
|
? { clientPort: Number(env.HMR_CLIENT_PORT), protocol: env.HMR_PROTOCOL || 'wss' }
|
|
: undefined
|
|
return mergeConfig(b, {
|
|
plugins: [publicGuard, rudderStarting],
|
|
cacheDir: '/tmp/vite-cache',
|
|
server: {
|
|
host: env.VITE_HOST || '0.0.0.0',
|
|
port: Number(env.VITE_PORT || 5173),
|
|
strictPort: true,
|
|
allowedHosts: true,
|
|
...(hmr ? { hmr } : {}),
|
|
...(PUBLIC ? { proxy: { '^/(cdn|api-prod|pb|admin)/': { target: 'http://127.0.0.1:5175', ws: true } } } : {}),
|
|
// Public: the checkout's git internals are not served either (the private history, .git/config). Vite's
|
|
// defaults are restated because setting deny replaces them.
|
|
...(PUBLIC ? { fs: { strict: true, deny: ['.env', '.env.*', '*.{crt,pem}', '**/.git/**'] } } : {}),
|
|
},
|
|
})
|
|
}
|