Files
TREK/server/tests/unit/docker-entrypoint.test.ts
T
Maurice 5d04f9c400 fix(docker): move start-up into a script so an edited container still boots
The image carried its whole start-up sequence as one inline `sh -c` string
containing four single-quoted echo messages. Container management UIs render
Config.Cmd back into a text field and re-split it on submit with a quote-aware,
escape-unaware tokenizer, so editing a container in place — the usual way to
change an environment variable — shredded the quoting and left dash half an
if-block to parse. The container then died before Node started, with only
`TREK: 1: Syntax error: end of file unexpected (expecting "fi")` in the log:
the word TREK is the stranded fragment of the first echo, reported as $0.

The logic moves to server/scripts/entrypoint.sh unchanged, and CMD becomes a
single bare token, which has nothing left to mangle. A unit test pins that
shape — both exec-form lines parse, and every argv element stays one token
free of quotes and spaces — and the Dockerfile joins the test workflow's path
filter so that guard actually runs when someone edits it.

Closes #2374
2026-09-20 12:48:44 +02:00

53 lines
2.3 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
// Container management UIs let you edit a running container's command, and they
// do it by rendering Config.Cmd back into a text field and re-splitting it with a
// quote-aware tokenizer on submit. Embedded quotes never survive that round-trip:
// an inline `sh -c "… echo 'FATAL: …' …"` comes back truncated and the container
// dies with a shell syntax error before Node starts (#2374). The image therefore
// keeps its start-up logic in server/scripts/entrypoint.sh and every ENTRYPOINT
// and CMD element is a single bare token. Pin that shape so it cannot drift back.
const repoRoot = path.resolve(__dirname, '../../..');
const dockerfile = fs.readFileSync(path.join(repoRoot, 'Dockerfile'), 'utf8');
const entrypointPath = path.join(repoRoot, 'server', 'scripts', 'entrypoint.sh');
// Anchored at column 0 and requiring the JSON exec form, so the HEALTHCHECK's
// indented shell-form `CMD wget -qO- … || exit 1` is not picked up.
const readExecForms = (): string[][] =>
[...dockerfile.matchAll(/^(?:ENTRYPOINT|CMD)[ \t]+(\[[^\n]*\])$/gm)].map((m) => JSON.parse(m[1]));
describe('container start-up shape', () => {
const execForms = readExecForms();
it('finds both exec-form lines (guards against matching nothing or the healthcheck)', () => {
expect(execForms).toHaveLength(2);
});
it('keeps every ENTRYPOINT and CMD element a single token free of quotes and spaces', () => {
for (const argv of execForms) {
expect(argv.length).toBeGreaterThan(0);
for (const element of argv) expect(element).toMatch(/^[^\s'"]+$/);
}
});
});
describe('entrypoint script', () => {
const source = fs.readFileSync(entrypointPath, 'utf8');
it('exists and is copied into the image by the Dockerfile', () => {
expect(fs.existsSync(entrypointPath)).toBe(true);
expect(dockerfile).toContain('COPY server/scripts/entrypoint.sh /usr/local/bin/trek-entrypoint');
});
it('has LF line endings (a CRLF shebang kills the container with "no such file or directory")', () => {
expect(source).not.toContain('\r');
});
it('keeps the volume-over-/app preflight wording the wiki quotes', () => {
expect(source).toContain('FATAL: TREK application files are missing from the image.');
});
});