const { execFileSync } = require('node:child_process') const { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } = require('node:fs') const path = require('node:path') const crypto = require('node:crypto') const database = 'yawan_native_acceptance_20260915' class AcceptanceConfigurationError extends Error {} function parseEnvironment(text) { return Object.fromEntries(text.split(/\r?\n/).filter(line => line && !line.startsWith('#') && line.includes('=')) .map(line => { const i = line.indexOf('='); return [line.slice(0, i), line.slice(i + 1)] })) } function resolveMilvusCollection(productionCollection, override) { const production = (productionCollection || '').trim() || 'weknora_embeddings' const acceptance = (override || '').trim() || 'yawan_acceptance_hybrid' if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(acceptance)) { throw new AcceptanceConfigurationError('ACCEPTANCE_MILVUS_COLLECTION must be a valid Milvus collection prefix') } // Milvus adds _; neither environment may select the other's family. const isDimensionCollection = (name, prefix) => name.startsWith(prefix) && /^_\d+$/.test(name.slice(prefix.length)) if (acceptance === production || isDimensionCollection(acceptance, production) || isDimensionCollection(production, acceptance)) { throw new AcceptanceConfigurationError('ACCEPTANCE_MILVUS_COLLECTION must be isolated from the production collection prefix and its dimension collections') } return acceptance } function buildAcceptanceEnvironment(production, previous = {}, overrides = {}, newJWTSecret = '') { const collection = resolveMilvusCollection(production.MILVUS_COLLECTION, overrides.ACCEPTANCE_MILVUS_COLLECTION) const secret = previous.JWT_SECRET || newJWTSecret if (!secret) throw new AcceptanceConfigurationError('An acceptance JWT_SECRET is required for the first start') return { ...production, DB_NAME: database, REDIS_DB: '14', REDIS_PREFIX: 'yawan-acceptance:', WEKNORA_REDIS_NAMESPACE: 'yawan-acceptance', JWT_SECRET: secret, WEKNORA_AUTH_REGISTRATION_MODE: 'invite_only', DISABLE_REGISTRATION: 'true', WEKNORA_TENANT_ENABLE_RBAC: 'true', WEKNORA_BOOTSTRAP_SYSTEM_ADMIN_EMAIL: overrides.ACCEPTANCE_BOOTSTRAP_SYSTEM_ADMIN_EMAIL || previous.WEKNORA_BOOTSTRAP_SYSTEM_ADMIN_EMAIL || '', RETRIEVE_DRIVER: overrides.ACCEPTANCE_RETRIEVE_DRIVER || 'milvus', MILVUS_COLLECTION: collection, ENABLE_GRAPH: 'false', LEGACY_GRAPH_ENABLE: 'true', LOG_FORMAT: '%d %level %logger %traceId | %msg', STORAGE_TYPE: 'local', LOCAL_STORAGE_BASE_DIR: '/data/files', RESUMABLE_UPLOAD_DIR: '/data/uploads', } } function main() { const deployment = path.resolve(__dirname, '../..') if (existsSync(path.join(deployment, 'public-release.json'))) { throw new AcceptanceConfigurationError('This data directory is serving the public system. Use the public release process; acceptance reset is disabled.') } const target = path.join(deployment, 'acceptance') const envPath = path.join(target, 'runtime.env') const docker = (...args) => execFileSync('docker', ['--context', 'colima-yawan', ...args], { encoding: 'utf8', maxBuffer: 4 * 1024 * 1024, stdio: ['ignore', 'pipe', 'pipe'], }).trim() const original = JSON.parse(docker('inspect', 'WeKnora-app'))[0] const production = parseEnvironment(original.Config.Env.join('\n')) let previous = {} try { previous = parseEnvironment(readFileSync(envPath, 'utf8')) } catch (error) { if (error.code !== 'ENOENT') throw error } const env = buildAcceptanceEnvironment(production, previous, process.env, previous.JWT_SECRET ? '' : crypto.randomBytes(32).toString('hex')) const network = Object.keys(original.NetworkSettings.Networks)[0] const exists = docker('exec', 'WeKnora-postgres', 'psql', '-U', env.DB_USER, '-d', 'postgres', '-Atc', `SELECT 1 FROM pg_database WHERE datname='${database}'`) if (!exists) docker('exec', 'WeKnora-postgres', 'createdb', '-U', env.DB_USER, '-T', 'template0', database) mkdirSync(target, { recursive: true }) mkdirSync(path.join(target, 'files'), { recursive: true }) mkdirSync(path.join(target, 'resumable-uploads'), { recursive: true }) writeFileSync(envPath, Object.entries(env).map(([key, value]) => `${key}=${value}`).join('\n') + '\n', { mode: 0o600 }) chmodSync(envPath, 0o600) const active = docker('ps', '-a', '--filter', 'name=^yawan-native-acceptance$', '--format', '{{.Names}}') if (active) docker('rm', '-f', 'yawan-native-acceptance') docker('run', '-d', '--name', 'yawan-native-acceptance', '--network', network, '--env-file', envPath, '-p', '127.0.0.1:18080:8080', '-v', `${path.join(deployment, 'src/WeKnora-native-acceptance')}:/app/WeKnora:ro`, '-v', `${path.join(deployment, 'src/migrations')}:/app/migrations:ro`, '-v', `${path.join(target, 'files')}:/data/files`, '-v', `${path.join(target, 'resumable-uploads')}:/data/uploads`, 'yawan-weknora-app:native-final2') console.log(JSON.stringify({ url: 'http://127.0.0.1:18080', database, redisDB: 14, status: 'started' })) } module.exports = { parseEnvironment, resolveMilvusCollection, buildAcceptanceEnvironment } if (require.main === module) { try { main() } catch (error) { // Docker/JSON errors can contain inspected environment values; never print them. console.error(error instanceof AcceptanceConfigurationError ? error.message : 'Acceptance runtime setup failed; check Docker availability and local file access') process.exitCode = 1 } }