Files

100 lines
5.4 KiB
JavaScript
Raw Permalink Normal View History

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 _<dimension>; 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
}
}