'use strict'; const fs = require('fs'); const os = require('os'); const path = require('path'); const { spawn, spawnSync } = require('child_process'); const bootstrapScript = path.resolve(__dirname, '..', '..', 'docker', 'bootstrap-admin-credentials.sh'); const hasPosixShell = spawnSync('sh', ['-c', 'exit 0'], { stdio: 'ignore' }).status === 0; function sourceBootstrap(env) { const scriptPath = bootstrapScript.replace(/\\/g, '/'); return spawnSync('sh', ['-c', `. "${scriptPath}"`], { env: { ...process.env, ...env }, encoding: 'utf8', }); } function sourceBootstrapExports(env) { const scriptPath = bootstrapScript.replace(/\\/g, '/'); return spawnSync( 'sh', [ '-c', `. "${scriptPath}" >/dev/null 2>&1; printf "%s|%s|%s|%s\\n" "$INIT_ADMIN_USER" "$DEFAULT_ADMIN_USERNAME" "$INIT_ADMIN_PASS" "$DEFAULT_ADMIN_PASSWORD"`, ], { env: { ...process.env, ...env }, encoding: 'utf8', } ); } (hasPosixShell ? describe : describe.skip)('Docker bootstrap-admin-credentials', () => { let tempDir; beforeEach(() => { tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'betterdesk-bootstrap-')); }); afterEach(() => { fs.rmSync(tempDir, { recursive: true, force: true }); }); test('publishes the configured password on a fresh primary store', () => { const result = sourceBootstrap({ RUSTDESK_PATH: tempDir, ADMIN_PASSWORD: 'configured-password-123', INIT_ADMIN_PASS: '', DEFAULT_ADMIN_PASSWORD: '', }); expect(result.status).toBe(0); const credentials = fs.readFileSync(path.join(tempDir, '.admin_credentials'), 'utf8'); expect(credentials).toContain('Admin Username: admin'); expect(credentials).toContain('Admin Password: configured-password-123'); expect(credentials).toContain('Generated by: BetterDesk Docker bootstrap (ADMIN_PASSWORD)'); }); test('refreshes stale credentials when the primary store is still empty', () => { fs.writeFileSync( path.join(tempDir, '.admin_credentials'), 'Admin Username: admin\nAdmin Password: stale-password\n', { mode: 0o600 } ); const result = sourceBootstrap({ RUSTDESK_PATH: tempDir, ADMIN_PASSWORD: 'replacement-password-123', INIT_ADMIN_PASS: '', DEFAULT_ADMIN_PASSWORD: '', }); expect(result.status).toBe(0); const credentials = fs.readFileSync(path.join(tempDir, '.admin_credentials'), 'utf8'); expect(credentials).toContain('Admin Password: replacement-password-123'); }); test('exports supervisord admin variables when no password is available', () => { const result = sourceBootstrapExports({ RUSTDESK_PATH: tempDir, DB_PATH: path.join(tempDir, 'missing.sqlite'), INIT_ADMIN_USER: '', INIT_ADMIN_PASS: '', DEFAULT_ADMIN_USERNAME: '', DEFAULT_ADMIN_PASSWORD: '', ADMIN_USERNAME: '', ADMIN_PASSWORD: '', }); expect(result.status).toBe(0); // Fresh volume with no ADMIN_PASSWORD: generate one shared secret and // export it for both Go (INIT_*) and Node (DEFAULT_*) supervisord vars. const parts = result.stdout.trim().split('|'); expect(parts).toHaveLength(4); expect(parts[0]).toBe('admin'); expect(parts[1]).toBe('admin'); expect(parts[2].length).toBeGreaterThan(0); expect(parts[3]).toBe(parts[2]); expect(fs.existsSync(path.join(tempDir, '.admin_credentials'))).toBe(true); }); test('keeps configured admin values synchronized across services', () => { const result = sourceBootstrapExports({ RUSTDESK_PATH: tempDir, INIT_ADMIN_USER: 'operator', INIT_ADMIN_PASS: 'configured-password-123', DEFAULT_ADMIN_USERNAME: '', DEFAULT_ADMIN_PASSWORD: '', ADMIN_USERNAME: '', ADMIN_PASSWORD: '', }); expect(result.status).toBe(0); expect(result.stdout.trim()).toBe( 'operator|operator|configured-password-123|configured-password-123' ); }); }); function runBootstrap(env) { return new Promise((resolve, reject) => { const child = spawn('sh', ['-c', '. "$BOOTSTRAP_SCRIPT"; printf "%s\\n" "$INIT_ADMIN_PASS" > "$RESULT_FILE"'], { env: { ...process.env, ...env, BOOTSTRAP_SCRIPT: bootstrapScript, }, stdio: 'ignore', }); child.on('error', reject); child.on('close', code => resolve(code)); }); } (hasPosixShell ? describe : describe.skip)('Docker bootstrap admin credentials', () => { let tempDir; beforeEach(() => { tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'betterdesk-bootstrap-')); }); afterEach(() => { fs.rmSync(tempDir, { recursive: true, force: true }); }); test('concurrent split entrypoints elect one shared password', async () => { const jobs = Array.from({ length: 8 }, (_, index) => runBootstrap({ RUSTDESK_PATH: tempDir, RESULT_FILE: path.join(tempDir, `result-${index}`), BOOTSTRAP_CREDENTIALS_WAIT_SECONDS: '10', INIT_ADMIN_PASS: '', DEFAULT_ADMIN_PASSWORD: '', ADMIN_PASSWORD: '', })); const codes = await Promise.all(jobs); expect(codes).toEqual(Array(8).fill(0)); const passwords = codes.map((_, index) => fs.readFileSync(path.join(tempDir, `result-${index}`), 'utf8').trim()); expect(new Set(passwords).size).toBe(1); const credentials = fs.readFileSync(path.join(tempDir, '.admin_credentials'), 'utf8'); expect(credentials).toContain(`Admin Password: ${passwords[0]}`); expect(fs.existsSync(path.join(tempDir, '.admin_credentials.lock'))).toBe(false); }); test('reuses an existing credentials file without replacing it', async () => { const credentialsPath = path.join(tempDir, '.admin_credentials'); const original = 'Admin Username: admin\nAdmin Password: existing-password\n'; fs.writeFileSync(credentialsPath, original, { mode: 0o600 }); const code = await runBootstrap({ RUSTDESK_PATH: tempDir, RESULT_FILE: path.join(tempDir, 'result'), INIT_ADMIN_PASS: '', DEFAULT_ADMIN_PASSWORD: '', ADMIN_PASSWORD: '', }); expect(code).toBe(0); expect(fs.readFileSync(path.join(tempDir, 'result'), 'utf8').trim()).toBe('existing-password'); expect(fs.readFileSync(credentialsPath, 'utf8')).toBe(original); }); });