Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/server/src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ export class Compiler {
config.dockerContainer,
'--restart',
'unless-stopped',
'--init',
'--cpus=2',
'--memory=1g',
'--pids-limit=128',
Expand Down Expand Up @@ -452,6 +453,7 @@ function workerMatches(containerJson: string, imageJson: string, jobsDir: string
container.Image === image.Id &&
container.Config?.Image === config.dockerImage &&
container.Config?.User === 'tectonic' &&
container.HostConfig?.Init === true &&
container.HostConfig?.ReadonlyRootfs === true &&
container.HostConfig?.Memory === 1024 * 1024 * 1024 &&
container.HostConfig?.PidsLimit === 128 &&
Expand Down
27 changes: 25 additions & 2 deletions apps/server/tests/compiler-docker.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
import { seedUser } from './helpers.js';
import { execFileSync } from 'node:child_process';
import { mkdtemp, readFile, rm } from 'node:fs/promises';
import { mkdtemp, readFile, realpath, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { expect, it, vi } from 'vitest';
import { buildApp } from './helpers.js';
import { config } from '../src/config.js';

it.skipIf(process.env.OVERROOT_TEST_DOCKER !== '1')('compiles a nested private source snapshot as the isolated Docker UID and reuses SyncTeX', async () => {
it.skipIf(process.env.OVERROOT_TEST_DOCKER !== '1')('replaces a legacy worker, reaps orphaned processes, compiles private sources and reuses SyncTeX', async () => {
const dir = await mkdtemp(join(tmpdir(), 'overroot-docker-'));
const previousContainer = config.dockerContainer;
const previousFake = config.fakeCompiler;
config.fakeCompiler = false;
config.dockerContainer = `overroot-tectonic-test-${process.pid}`;
const { app, context } = await buildApp({ databasePath: join(dir, 'test.sqlite3'), logger: false });
try {
// Match every existing worker requirement except init, reproducing pre-fix containers.
execFileSync('docker', ['build', '-t', config.dockerImage, config.dockerfileDir], { timeout: 10 * 60_000 });
const legacyId = execFileSync('docker', [
'run', '-d', '--name', config.dockerContainer, '--restart', 'unless-stopped',
'--cpus=2', '--memory=1g', '--pids-limit=128', '--cap-drop=ALL',
'--security-opt=no-new-privileges', '--read-only', '--tmpfs', '/tmp:rw,nosuid,nodev,size=256m',
'-v', 'overroot-tectonic-cache:/var/cache/tectonic',
'-v', `${await realpath(join(dir, 'jobs'))}:/work:rw`, config.dockerImage
], { encoding: 'utf8', timeout: 30_000 }).trim();
const user = seedUser(context.db, 'compiler-test', true);
const modules = Array.from({ length: 20 }, (_, i) => ({
path: `sections/deep/chapter-${i % 4}/part-${i}.tex`,
Expand Down Expand Up @@ -44,6 +53,20 @@ it.skipIf(process.env.OVERROOT_TEST_DOCKER !== '1')('compiles a nested private s
expect(result.status, result.error ?? result.log).toBe('succeeded');
expect((await readFile(result.pdf_path!)).subarray(0, 5).toString()).toBe('%PDF-');
expect(result.synctex_path).not.toBeNull();
const worker = JSON.parse(execFileSync('docker', ['inspect', config.dockerContainer], { encoding: 'utf8', timeout: 10_000 }))[0];
expect(worker.Id).not.toBe(legacyId);
expect(worker.HostConfig.Init).toBe(true);
// The shell exits first; its child is adopted by PID 1 and must be reaped after exit.
const orphanPid = execFileSync('docker', [
'exec', config.dockerContainer, 'sh', '-c', 'sleep 1 </dev/null >/dev/null 2>&1 & echo $!'
], { encoding: 'utf8', timeout: 10_000 }).trim();
expect(orphanPid).toMatch(/^\d+$/);
await vi.waitFor(() => {
expect(execFileSync('docker', [
'exec', config.dockerContainer, 'sh', '-c',
'if [ -e /proc/"$1" ]; then echo present; else echo reaped; fi', 'sh', orphanPid
], { encoding: 'utf8', timeout: 5_000 }).trim()).toBe('reaped');
}, { timeout: 10_000, interval: 250 });
context.projects.renameFile(body.id, body.path);
const cached = await context.compiler.request(project, actor);
expect(cached.status).toBe('succeeded');
Expand Down
22 changes: 22 additions & 0 deletions deploy/uptime/overroot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Read-only Overroot responses and compiler init configuration; install in ~/uptime/."""
from _probe import main

SPEC = {
'name': 'Overroot LaTeX studio',
'group': 'Apps',
'scope': 'Origin/public frontend and status API, dedicated Cloudflare connector readiness, and compiler container running with init. Does not log in, read documents, edit, or compile LaTeX.',
'probes': [
{'kind': 'http', 'name': 'Origin health', 'url': 'http://127.0.0.1:4317/health', 'equals': {'ok': True}},
{'kind': 'http', 'name': 'Connector ready', 'url': 'http://127.0.0.1:20417/ready', 'equals': {'status': 200}, 'keys': ['readyConnections']},
{'kind': 'http', 'name': 'Public API', 'url': 'https://overroot.toni.foo/api/v1/status', 'equals': {'ok': True, 'name': 'Overroot', 'publicUrl': 'https://overroot.toni.foo', 'identityOrigin': 'https://accounts.toni.foo'}},
{'kind': 'http', 'name': 'Public frontend', 'url': 'https://overroot.toni.foo/', 'contains': '<title>Overroot</title>'},
{'kind': 'command', 'name': 'Compiler worker init',
'argv': ['docker', 'inspect', '--format', '{"running":{{json .State.Running}},"init":{{json .HostConfig.Init}}}', 'overroot-tectonic-worker'],
'equals': {'running': True, 'init': True}},
],
'boot': {'user': ['overroot.service', 'overroot-cloudflared.service'], 'system': [], 'containers': ['overroot-tectonic-worker']},
'link': 'https://overroot.toni.foo/',
}

if __name__ == '__main__':
main(SPEC)
Loading