sync: update from private repo (8447335)
CI / build-and-test (push) Has been cancelled

This commit is contained in:
oss-sync
2026-06-05 06:25:26 +00:00
parent e00ea9fb0c
commit 42031b18b0
8 changed files with 589 additions and 8 deletions
+157
View File
@@ -0,0 +1,157 @@
#!/usr/bin/env node
// Interactive (and --yes non-interactive) setup wizard. Generates a minimal
// config.yaml (+ .env PORT) for a single LLM connection. Run before building:
// npm run setup
import { createInterface } from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
import {
existsSync, readFileSync, openSync, writeFileSync, fsyncSync, closeSync,
renameSync, copyFileSync, chmodSync, unlinkSync,
} from 'node:fs';
import { join } from 'node:path';
import {
parseEndpoint, buildWorkerEntry, renderConfigYaml, renderDotenv,
parseAnswersFromEnv, probeModels,
} from './setup-lib.mjs';
const ROOT = process.cwd();
const CONFIG_PATH = join(ROOT, 'config.yaml');
const ENV_PATH = join(ROOT, '.env');
const maskKey = (k) => (k ? `${String(k).slice(0, 6)}****` : '');
function atomicWrite(path, content, mode) {
const tmp = `${path}.${process.pid}.tmp`;
const fd = openSync(tmp, 'w', mode);
try { writeFileSync(fd, content); fsyncSync(fd); } finally { closeSync(fd); }
try { chmodSync(tmp, mode); } catch (e) { console.warn(`⚠ chmod ${tmp} failed (${e.message}); continuing`); }
try { renameSync(tmp, path); } catch (e) { try { unlinkSync(tmp); } catch {} throw e; }
}
function backupExistingConfig() {
const bak = `${CONFIG_PATH}.bak`;
copyFileSync(CONFIG_PATH, bak); // copy (not rename) so original survives a later failure
try { chmodSync(bak, 0o600); } catch (e) { console.warn(`⚠ chmod ${bak} failed (${e.message})`); }
console.log(` 既存 config.yaml を ${bak} (0600) に退避しました。`);
}
function writeOutputs({ worker, port }) {
// config.yaml is always 0600 (may hold an api_key).
atomicWrite(CONFIG_PATH, renderConfigYaml(worker), 0o600);
const existingEnv = existsSync(ENV_PATH) ? readFileSync(ENV_PATH, 'utf-8') : '';
atomicWrite(ENV_PATH, renderDotenv(existingEnv, { port }), 0o644);
}
function printDone({ port, worker }) {
console.log('');
console.log('✅ config.yaml を生成しました(詳細設定は config.yaml.example か Settings UI を参照)。');
if (worker.api_key) console.log(` gateway api_key: ${maskKey(worker.api_key)}config.yaml に保存、権限 0600`);
console.log('次の手順:');
console.log(' scripts/build-all.sh');
console.log(' scripts/server.sh start');
console.log(` ブラウザで http://localhost:${port}`);
}
async function runNonInteractive(force) {
const { answers, error } = parseAnswersFromEnv(process.env);
if (error) { console.error(`setup --yes: ${error}`); process.exit(1); }
if (existsSync(CONFIG_PATH) && !force) {
console.error('setup --yes: config.yaml already exists; pass --force to overwrite.');
process.exit(1);
}
const probe = await probeModels(answers); // non-fatal: model already provided via env
if (!probe.ok) console.warn(`⚠ LLM probe failed (${probe.error}); continuing (SETUP_MODEL is set).`);
const worker = buildWorkerEntry({
connectionType: answers.connectionType, endpoint: answers.endpoint, model: answers.model, apiKey: answers.apiKey,
});
if (existsSync(CONFIG_PATH)) backupExistingConfig();
writeOutputs({ worker, port: answers.port });
printDone({ port: answers.port, worker });
}
async function ask(rl, question, def) {
const suffix = def !== undefined && def !== '' ? ` [${def}]` : '';
const a = (await rl.question(`${question}${suffix}: `)).trim();
return a || (def ?? '');
}
async function runInteractive() {
const rl = createInterface({ input, output });
try {
if (existsSync(CONFIG_PATH)) {
const yn = (await ask(rl, '既存の config.yaml を上書きしますか? 既存は config.yaml.bak に退避します (y/N)', 'N'));
if (!/^y(es)?$/i.test(yn)) { console.log('中止しました(変更なし)。'); return; }
}
// connection type
let connectionType = 'direct';
const ct = await ask(rl, '接続タイプ 1) direct (Ollama/vLLM 等) 2) aao_gateway (別 MAESTRO Gateway)', '1');
connectionType = ct === '2' || ct === 'aao_gateway' ? 'aao_gateway' : 'direct';
// endpoint
let endpoint, base;
const defEndpoint = connectionType === 'direct' ? 'http://localhost:11434/v1' : '';
const hint = connectionType === 'aao_gateway' ? '(例: http://gateway-host:9876/v1' : '';
for (;;) {
const raw = await ask(rl, `LLM endpoint URL${hint}`, defEndpoint);
const r = parseEndpoint(raw);
if (r.error) { console.log(`${r.error}`); continue; }
({ endpoint, base } = r);
break;
}
// api key (gateway only)
let apiKey;
if (connectionType === 'aao_gateway') {
for (;;) {
apiKey = await ask(rl, 'gateway API キー (sk-aao-...)', '');
if (apiKey) break;
console.log(' ⚠ aao_gateway では API キーが必須です。');
}
}
// probe + model
console.log(' 接続を確認しています...');
const probe = await probeModels({ endpoint, base, apiKey });
let model;
if (probe.ok && probe.models.length > 0) {
console.log(' 利用可能なモデル:');
probe.models.forEach((m, i) => console.log(` ${i + 1}) ${m}`));
for (;;) {
const sel = await ask(rl, `モデルを選択 (1-${probe.models.length}, または名前を直接入力)`, '1');
const idx = Number(sel);
if (Number.isInteger(idx) && idx >= 1 && idx <= probe.models.length) { model = probe.models[idx - 1]; break; }
if (sel) { model = sel; break; }
}
} else {
if (!probe.ok) console.log(` ⚠ 接続できませんでした (${probe.error})。後で config を直せます。`);
else console.log(' ⚠ モデル一覧が空でした。');
for (;;) { model = await ask(rl, 'モデル名を手入力', ''); if (model) break; }
}
// server port
let port;
for (;;) {
const p = Number(await ask(rl, 'MAESTRO サーバーの listen port', '9876'));
if (Number.isInteger(p) && p >= 1 && p <= 65535) { port = p; break; }
console.log(' ⚠ port は 1-65535 の整数で入力してください。');
}
const worker = buildWorkerEntry({ connectionType, endpoint, model, apiKey });
if (existsSync(CONFIG_PATH)) backupExistingConfig();
writeOutputs({ worker, port });
printDone({ port, worker });
} finally {
rl.close();
}
}
async function main() {
const argv = process.argv.slice(2);
const yes = argv.includes('--yes');
const force = argv.includes('--force');
if (yes) await runNonInteractive(force);
else await runInteractive();
}
main().catch((e) => { console.error(`setup failed: ${e && e.message ? e.message : e}`); process.exit(1); });