diff --git a/Dockerfile b/Dockerfile index f6cafa437b..4af861cc33 100644 --- a/Dockerfile +++ b/Dockerfile @@ -113,6 +113,7 @@ ADD bower_components /grist/bower_components ADD sandbox /grist/sandbox ADD plugins /grist/plugins ADD static /grist/static +ADD docker-runner.mjs /grist/docker-runner.mjs # Make optional pyodide sandbox available COPY --from=builder /grist/sandbox/pyodide /grist/sandbox/pyodide @@ -152,4 +153,4 @@ ENV \ EXPOSE 8484 ENTRYPOINT ["/usr/bin/tini", "-s", "--"] -CMD ["./sandbox/run.sh"] +CMD ["node", "./sandbox/supervisor.mjs"] diff --git a/app/server/lib/FlexServer.ts b/app/server/lib/FlexServer.ts index 46e4a50847..5aed483d0a 100644 --- a/app/server/lib/FlexServer.ts +++ b/app/server/lib/FlexServer.ts @@ -1883,6 +1883,22 @@ export class FlexServer implements GristServer { const probes = new BootProbes(this.app, this, '/api', adminMiddleware); probes.addEndpoints(); + this.app.post('/api/admin/restart', requireInstallAdmin, expressWrap(async (req, resp) => { + const newConfig = req.body.newConfig; + resp.on('finish', () => { + // If we have IPC with parent process (e.g. when running under + // Docker) tell the parent that we have a new environment so it + // can restart us. + if (process.send) { + process.send({ action: 'restart', newConfig }); + } + }); + // On the topic of http response codes, thus spake MDN: + // "409: This response is sent when a request conflicts with the current state of the server." + const status = process.send ? 200 : 409; + return resp.status(status).send(); + })); + // Restrict this endpoint to install admins this.app.get('/api/install/prefs', requireInstallAdmin, expressWrap(async (_req, resp) => { const activation = await this._activations.current(); diff --git a/sandbox/supervisor.mjs b/sandbox/supervisor.mjs new file mode 100644 index 0000000000..2508cb1705 --- /dev/null +++ b/sandbox/supervisor.mjs @@ -0,0 +1,35 @@ +import {spawn} from 'child_process'; + +let grist; + +function startGrist(newConfig={}) { + saveNewConfig(newConfig); + // H/T https://stackoverflow.com/a/36995148/11352427 + grist = spawn('./sandbox/run.sh', { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'] + }); + grist.on('message', function(data) { + if (data.action === 'restart') { + console.log('Restarting Grist with new environment'); + + // Note that we only set this event handler here, after we have + // a new environment to reload with. Small chance of a race here + // in case something else sends a SIGINT before we do it + // ourselves further below. + grist.on('exit', () => { + grist = startGrist(data.newConfig); + }); + + grist.kill('SIGINT'); + } + }); + return grist; +} + +// Stub function +function saveNewConfig(newConfig) { + // TODO: something here to actually persist the new config before + // restarting Grist. +} + +startGrist();