Merge branch 'main' into setup-scratch-agent-cleanup
This commit is contained in:
33
nanoclaw.sh
33
nanoclaw.sh
@@ -137,6 +137,39 @@ write_header
|
||||
# NANOCLAW_BOOTSTRAPPED=1 and skips re-printing the wordmark.
|
||||
cat "$PROJECT_ROOT/assets/setup-splash.txt"
|
||||
|
||||
# ─── pre-flight: root user warning (Linux) ────────────────────────────
|
||||
if [ "$(uname -s)" = "Linux" ] && [ "$(id -u)" -eq 0 ]; then
|
||||
printf ' %s\n' \
|
||||
"$(red 'Warning: you are running as root.')"
|
||||
printf ' %s\n' \
|
||||
"$(dim "Running NanoClaw as root is not recommended. It can cause permission")"
|
||||
printf ' %s\n\n' \
|
||||
"$(dim "issues with containers, services, and file ownership.")"
|
||||
printf ' %s\n' "$(bold '1)') $(dim 'Show me instructions for creating a new Linux user')"
|
||||
printf ' %s\n\n' "$(bold '2)') $(dim 'Continue setting up NanoClaw as root user (not recommended)')"
|
||||
read -r -p " $(bold 'Choose [1/2]: ')" ROOT_ANS </dev/tty
|
||||
|
||||
case "${ROOT_ANS:-1}" in
|
||||
2)
|
||||
ph_event setup_root_continued
|
||||
printf '\n'
|
||||
;;
|
||||
*)
|
||||
ph_event setup_root_aborted
|
||||
printf '\n %s\n' "$(bold 'To set up a regular user (via SSH):')"
|
||||
printf ' %s\n\n' "$(dim 'Not using SSH? Refer to your hosting provider docs or ask your coding agent to help you set up SSH access.')"
|
||||
printf ' %s\n' "$(dim '1. Create a new user: adduser nanoclaw')"
|
||||
printf ' %s\n' "$(dim '2. Add to sudo group: usermod -aG sudo nanoclaw')"
|
||||
printf ' %s\n' "$(dim '3. Enable passwordless sudo: echo "nanoclaw ALL=(ALL) NOPASSWD:ALL" | tee /etc/sudoers.d/nanoclaw')"
|
||||
printf ' %s\n' "$(dim '4. Log out: exit')"
|
||||
printf ' %s\n' "$(dim '5. Log back in as the new user: ssh nanoclaw@your-server')"
|
||||
printf ' %s\n' "$(dim '6. Clone the repo: git clone https://github.com/qwibitai/nanoclaw.git && cd nanoclaw')"
|
||||
printf ' %s\n\n' "$(dim '7. Re-run setup: bash nanoclaw.sh')"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# ─── pre-flight: Homebrew on macOS ─────────────────────────────────────
|
||||
# setup/install-node.sh and setup/install-docker.sh both require `brew` on
|
||||
# macOS. On a factory Mac there's no brew, and those helpers would fail
|
||||
|
||||
@@ -85,17 +85,21 @@ async function main(): Promise<void> {
|
||||
|
||||
// Welcome menu — default path or open advanced overrides before any setup
|
||||
// work begins. Default lands on standard so Enter is the happy path.
|
||||
const startChoice = ensureAnswer(
|
||||
await brightSelect<'default' | 'advanced'>({
|
||||
message: 'How would you like to begin?',
|
||||
options: [
|
||||
{ value: 'default', label: 'Standard setup' },
|
||||
{ value: 'advanced', label: 'Advanced', hint: 'override defaults' },
|
||||
],
|
||||
initialValue: 'default',
|
||||
}),
|
||||
) as 'default' | 'advanced';
|
||||
setupLog.userInput('start_choice', startChoice);
|
||||
// On sg re-exec, the user already chose — skip straight to standard.
|
||||
let startChoice: 'default' | 'advanced' = 'default';
|
||||
if (process.env.NANOCLAW_REEXEC_SG !== '1') {
|
||||
startChoice = ensureAnswer(
|
||||
await brightSelect<'default' | 'advanced'>({
|
||||
message: 'How would you like to begin?',
|
||||
options: [
|
||||
{ value: 'default', label: 'Standard setup' },
|
||||
{ value: 'advanced', label: 'Advanced', hint: 'override defaults' },
|
||||
],
|
||||
initialValue: 'default',
|
||||
}),
|
||||
) as 'default' | 'advanced';
|
||||
setupLog.userInput('start_choice', startChoice);
|
||||
}
|
||||
if (startChoice === 'advanced') {
|
||||
configValues = await runAdvancedScreen(configValues);
|
||||
applyToEnv(configValues);
|
||||
@@ -1147,9 +1151,11 @@ function maybeReexecUnderSg(): void {
|
||||
if (spawnSync('which', ['sg'], { stdio: 'ignore' }).status !== 0) return;
|
||||
|
||||
p.log.warn(brandBody('Docker socket not accessible in current group. Re-executing under `sg docker`.'));
|
||||
const existingSkip = (process.env.NANOCLAW_SKIP ?? '').split(',').map((s) => s.trim()).filter(Boolean);
|
||||
const skipList = [...new Set([...existingSkip, ...setupLog.completedStepNames()])].join(',');
|
||||
const res = spawnSync('sg', ['docker', '-c', 'pnpm run setup:auto'], {
|
||||
stdio: 'inherit',
|
||||
env: { ...process.env, NANOCLAW_REEXEC_SG: '1' },
|
||||
env: { ...process.env, NANOCLAW_REEXEC_SG: '1', ...(skipList ? { NANOCLAW_SKIP: skipList } : {}) },
|
||||
});
|
||||
process.exit(res.status ?? 1);
|
||||
}
|
||||
|
||||
@@ -127,11 +127,22 @@ export async function run(args: string[]): Promise<void> {
|
||||
}
|
||||
|
||||
// Socket is unreachable due to group perms — current shell's supplementary
|
||||
// groups are fixed at login, so `usermod -aG docker` (via install-docker.sh
|
||||
// or a prior install) doesn't affect us until next login. Re-exec this
|
||||
// step under `sg docker` so the child picks up docker as its primary
|
||||
// group and can talk to /var/run/docker.sock without a logout.
|
||||
// groups are fixed at login, so `usermod -aG docker` doesn't affect us
|
||||
// until next login. Ensure the user is in the docker group (install-docker.sh
|
||||
// does this on fresh installs, but skips when Docker is already present),
|
||||
// then re-exec under `sg docker` so the child picks up docker as its
|
||||
// primary group and can talk to /var/run/docker.sock without a logout.
|
||||
if (status === 'no-permission' && getPlatform() === 'linux' && commandExists('sg')) {
|
||||
// Ensure the current user is in the docker group — without this,
|
||||
// sg will ask for the (typically unset) group password and fail.
|
||||
const inGroup = spawnSync('id', ['-nG'], { encoding: 'utf-8' });
|
||||
if (!(inGroup.stdout ?? '').split(/\s+/).includes('docker')) {
|
||||
log.info('Adding current user to docker group');
|
||||
spawnSync('sudo', ['usermod', '-aG', 'docker', process.env.USER ?? ''], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
}
|
||||
|
||||
log.info('Re-executing container step under `sg docker`');
|
||||
const res = spawnSync(
|
||||
'sg',
|
||||
|
||||
Reference in New Issue
Block a user