mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-10-25 14:00:53 +00:00
602c4e798b
Refs #424 Thanks: INSOLVE (Honorary); Marco Jakobs (@jacotec); MyNameisStitch (@MyNameisStitch); Redspin (@playerumpknow)
143 lines
5.8 KiB
JavaScript
143 lines
5.8 KiB
JavaScript
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const repoRoot = path.resolve(__dirname, '..', '..');
|
|
const dockerfiles = ['Dockerfile', 'Dockerfile.console', 'Dockerfile.server'];
|
|
|
|
function copySources(dockerfile) {
|
|
return fs.readFileSync(path.join(repoRoot, dockerfile), 'utf8')
|
|
.split(/\r?\n/)
|
|
.filter(line => /^\s*COPY\s/.test(line) && !line.includes('--from='))
|
|
.flatMap(line => {
|
|
const tokens = line.trim().split(/\s+/).slice(1);
|
|
return tokens.slice(0, -1);
|
|
});
|
|
}
|
|
|
|
function sourceExists(source) {
|
|
if (!/[?*[\]{}]/.test(source)) {
|
|
return fs.existsSync(path.join(repoRoot, source));
|
|
}
|
|
|
|
const wildcardIndex = source.search(/[?*[\]{}]/);
|
|
const directory = path.dirname(source.slice(0, wildcardIndex));
|
|
const prefix = path.basename(source.slice(0, wildcardIndex));
|
|
const absoluteDirectory = path.join(repoRoot, directory);
|
|
|
|
return fs.existsSync(absoluteDirectory)
|
|
&& fs.readdirSync(absoluteDirectory).some(entry => entry.startsWith(prefix));
|
|
}
|
|
|
|
describe('Docker build context contract', () => {
|
|
test.each(dockerfiles)('%s only copies sources present in the repository', dockerfile => {
|
|
const missing = copySources(dockerfile).filter(source => !sourceExists(source));
|
|
|
|
expect(missing).toEqual([]);
|
|
});
|
|
|
|
test.each(dockerfiles)('%s does not require withdrawn agent sources', dockerfile => {
|
|
const contents = fs.readFileSync(path.join(repoRoot, dockerfile), 'utf8');
|
|
|
|
expect(contents).not.toMatch(/betterdesk-(?:support-)?agent|agent-source/);
|
|
});
|
|
});
|
|
|
|
describe('Docker admin bootstrap contract', () => {
|
|
const composeFiles = [
|
|
'docker-compose.yml',
|
|
'docker-compose.single.yml',
|
|
'docker-compose.quick.yml',
|
|
'docker-compose.quick.single.yml',
|
|
'docker-compose.quick.macvlan.yml',
|
|
'docker-compose.quick.single.macvlan.yml',
|
|
];
|
|
|
|
test.each(composeFiles)('%s forwards all admin password aliases', composeFile => {
|
|
const contents = fs.readFileSync(path.join(repoRoot, composeFile), 'utf8');
|
|
|
|
expect(contents).toContain('ADMIN_PASSWORD=${ADMIN_PASSWORD:-}');
|
|
expect(contents).toContain('INIT_ADMIN_PASS=${ADMIN_PASSWORD:-}');
|
|
expect(contents).toContain('DEFAULT_ADMIN_PASSWORD=${ADMIN_PASSWORD:-}');
|
|
});
|
|
|
|
test('split Compose variants use the shared primary SQLite path', () => {
|
|
for (const composeFile of ['docker-compose.quick.yml', 'docker-compose.quick.macvlan.yml']) {
|
|
const contents = fs.readFileSync(path.join(repoRoot, composeFile), 'utf8');
|
|
|
|
expect(contents).toContain('DB_URL=/opt/rustdesk/db_v2.sqlite3');
|
|
expect(contents).toContain('DB_PATH=/opt/rustdesk/db_v2.sqlite3');
|
|
}
|
|
});
|
|
|
|
test('split server entrypoint normalizes the Go database path', () => {
|
|
const contents = fs.readFileSync(
|
|
path.join(repoRoot, 'docker', 'server-entrypoint.sh'),
|
|
'utf8'
|
|
);
|
|
|
|
expect(contents).toContain('export DB_URL="${DB_PATH:-${RUSTDESK_PATH:-$DATA_DIR}/db_v2.sqlite3}"');
|
|
expect(contents).toContain('export DB_URL="${DATABASE_URL:-${DB_PATH:-${RUSTDESK_PATH:-$DATA_DIR}/db_v2.sqlite3}}"');
|
|
});
|
|
|
|
test('hardened Docker checks app-owned paths as betterdesk', () => {
|
|
const bootstrap = fs.readFileSync(
|
|
path.join(repoRoot, 'docker', 'bootstrap-admin-credentials.sh'),
|
|
'utf8'
|
|
);
|
|
const entrypoint = fs.readFileSync(
|
|
path.join(repoRoot, 'docker', 'entrypoint.sh'),
|
|
'utf8'
|
|
);
|
|
const guard = fs.readFileSync(
|
|
path.join(repoRoot, 'docker', 'guard-sqlite-auth-split.sh'),
|
|
'utf8'
|
|
);
|
|
|
|
expect(bootstrap).toContain('file_exists_as_betterdesk()');
|
|
expect(bootstrap).toContain('path_exists_as_betterdesk()');
|
|
expect(bootstrap).toContain('file_exists_as_betterdesk "$_file"');
|
|
expect(bootstrap).toContain('file_exists_as_betterdesk "$_primary_db"');
|
|
expect(bootstrap).toContain('path_exists_as_betterdesk "$CREDS_FILE"');
|
|
expect(entrypoint).toContain('file_exists_as_betterdesk "$API_KEY_FILE"');
|
|
expect(entrypoint).toContain('file_exists_as_betterdesk "$ENROLLMENT_SENTINEL"');
|
|
expect(guard).toContain('file_exists_as_betterdesk "$_auth_db"');
|
|
expect(guard).toContain('file_exists_as_betterdesk "$_primary_db"');
|
|
});
|
|
|
|
test('AIO supervisord admin variables are always exported by bootstrap', () => {
|
|
const bootstrap = fs.readFileSync(
|
|
path.join(repoRoot, 'docker', 'bootstrap-admin-credentials.sh'),
|
|
'utf8'
|
|
);
|
|
const supervisor = fs.readFileSync(
|
|
path.join(repoRoot, 'docker', 'supervisord.conf'),
|
|
'utf8'
|
|
);
|
|
|
|
expect(bootstrap).toContain('ensure_admin_exports()');
|
|
expect(bootstrap).toContain('export INIT_ADMIN_USER=');
|
|
expect(bootstrap).toContain('export INIT_ADMIN_PASS=');
|
|
expect(bootstrap).toContain('export DEFAULT_ADMIN_USERNAME=');
|
|
expect(bootstrap).toContain('export DEFAULT_ADMIN_PASSWORD=');
|
|
expect(supervisor).toContain('%(ENV_DEFAULT_ADMIN_USERNAME)s');
|
|
expect(supervisor).toContain('%(ENV_DEFAULT_ADMIN_PASSWORD)s');
|
|
expect(supervisor).toContain('%(ENV_INIT_ADMIN_USER)s');
|
|
expect(supervisor).toContain('%(ENV_INIT_ADMIN_PASS)s');
|
|
});
|
|
|
|
test.each([
|
|
'docker-compose.yml',
|
|
'docker-compose.single.yml',
|
|
'docker-compose.quick.yml',
|
|
'docker-compose.quick.single.yml',
|
|
'docker-compose.quick.macvlan.yml',
|
|
'docker-compose.quick.single.macvlan.yml',
|
|
])('%s exposes optional TRUST_PROXY configuration', composeFile => {
|
|
const contents = fs.readFileSync(path.join(repoRoot, composeFile), 'utf8');
|
|
|
|
expect(contents).toContain('TRUST_PROXY=${TRUST_PROXY:-}');
|
|
});
|
|
});
|