mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-06-04 10:14:47 +08:00
712a0e1e01
Adds three allowlist-friendly setup helpers so /new-setup and /new-setup-2 don't hit unmatchable commands during a fresh install: - setup/install-node.sh — idempotent Node 22 install wrapper (macOS via brew, Linux via NodeSource + apt). Replaces the raw `curl | sudo -E bash -` flow whose stdin-consuming `bash -` segment can't be pre-approved. - setup/install-docker.sh — same pattern for Docker (brew --cask on macOS, get.docker.com on Linux + usermod). - setup/set-env.ts — generic `--step set-env` that writes KEY=VALUE to .env (and optionally syncs to data/env/env) so channel-install flows don't invent `grep && sed && rm` pipelines, which split at each && and can't be tightly allowlisted. new-setup-2's Telegram path now uses set-env for TELEGRAM_BOT_TOKEN and explicitly skips /add-telegram's Credentials section. new-setup step 1 and step 2 now call the install wrappers; the raw curl/apt entries are gone from the allowed-tools list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
78 lines
2.2 KiB
TypeScript
78 lines
2.2 KiB
TypeScript
/**
|
|
* Step: set-env — Write or update a KEY=VALUE in .env, with optional sync to
|
|
* data/env/env (the container-mounted copy).
|
|
*
|
|
* Usage:
|
|
* pnpm exec tsx setup/index.ts --step set-env -- \
|
|
* --key TELEGRAM_BOT_TOKEN --value "<token>" [--sync-container]
|
|
*
|
|
* Exists so channel-install flows don't have to invent grep/sed/rm pipelines
|
|
* (which can't be allowlisted tightly — sed can read any file, and each
|
|
* segment of an && chain is matched separately).
|
|
*
|
|
* Logs the key but never the value.
|
|
*/
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
import { log } from '../src/log.js';
|
|
import { emitStatus } from './status.js';
|
|
|
|
export async function run(args: string[]): Promise<void> {
|
|
const keyIdx = args.indexOf('--key');
|
|
const valueIdx = args.indexOf('--value');
|
|
const syncContainer = args.includes('--sync-container');
|
|
|
|
if (keyIdx === -1 || !args[keyIdx + 1]) {
|
|
throw new Error('--key <KEY> is required');
|
|
}
|
|
if (valueIdx === -1 || args[valueIdx + 1] === undefined) {
|
|
throw new Error('--value <VALUE> is required');
|
|
}
|
|
|
|
const key = args[keyIdx + 1];
|
|
const value = args[valueIdx + 1];
|
|
|
|
if (!/^[A-Z][A-Z0-9_]*$/.test(key)) {
|
|
throw new Error(`Invalid env key: ${key} (must be UPPER_SNAKE_CASE)`);
|
|
}
|
|
|
|
const projectRoot = process.cwd();
|
|
const envFile = path.join(projectRoot, '.env');
|
|
|
|
let content = '';
|
|
if (fs.existsSync(envFile)) {
|
|
content = fs.readFileSync(envFile, 'utf-8');
|
|
}
|
|
|
|
const lineRegex = new RegExp(`^${key}=.*$`, 'm');
|
|
const newLine = `${key}=${value}`;
|
|
const existed = lineRegex.test(content);
|
|
|
|
if (existed) {
|
|
content = content.replace(lineRegex, newLine);
|
|
} else {
|
|
const sep = content && !content.endsWith('\n') ? '\n' : '';
|
|
content = content + sep + newLine + '\n';
|
|
}
|
|
|
|
fs.writeFileSync(envFile, content);
|
|
log.info('Updated .env', { key, existed });
|
|
|
|
let synced = false;
|
|
if (syncContainer) {
|
|
const dataEnvDir = path.join(projectRoot, 'data', 'env');
|
|
fs.mkdirSync(dataEnvDir, { recursive: true });
|
|
fs.copyFileSync(envFile, path.join(dataEnvDir, 'env'));
|
|
synced = true;
|
|
log.info('Synced .env to container mount', { path: 'data/env/env' });
|
|
}
|
|
|
|
emitStatus('SET_ENV', {
|
|
KEY: key,
|
|
EXISTED: existed,
|
|
SYNCED_TO_CONTAINER: synced,
|
|
STATUS: 'success',
|
|
});
|
|
}
|