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.
|
# NANOCLAW_BOOTSTRAPPED=1 and skips re-printing the wordmark.
|
||||||
cat "$PROJECT_ROOT/assets/setup-splash.txt"
|
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 ─────────────────────────────────────
|
# ─── pre-flight: Homebrew on macOS ─────────────────────────────────────
|
||||||
# setup/install-node.sh and setup/install-docker.sh both require `brew` on
|
# 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
|
# macOS. On a factory Mac there's no brew, and those helpers would fail
|
||||||
|
|||||||
@@ -85,7 +85,10 @@ async function main(): Promise<void> {
|
|||||||
|
|
||||||
// Welcome menu — default path or open advanced overrides before any setup
|
// Welcome menu — default path or open advanced overrides before any setup
|
||||||
// work begins. Default lands on standard so Enter is the happy path.
|
// work begins. Default lands on standard so Enter is the happy path.
|
||||||
const startChoice = ensureAnswer(
|
// 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'>({
|
await brightSelect<'default' | 'advanced'>({
|
||||||
message: 'How would you like to begin?',
|
message: 'How would you like to begin?',
|
||||||
options: [
|
options: [
|
||||||
@@ -96,6 +99,7 @@ async function main(): Promise<void> {
|
|||||||
}),
|
}),
|
||||||
) as 'default' | 'advanced';
|
) as 'default' | 'advanced';
|
||||||
setupLog.userInput('start_choice', startChoice);
|
setupLog.userInput('start_choice', startChoice);
|
||||||
|
}
|
||||||
if (startChoice === 'advanced') {
|
if (startChoice === 'advanced') {
|
||||||
configValues = await runAdvancedScreen(configValues);
|
configValues = await runAdvancedScreen(configValues);
|
||||||
applyToEnv(configValues);
|
applyToEnv(configValues);
|
||||||
@@ -1147,9 +1151,11 @@ function maybeReexecUnderSg(): void {
|
|||||||
if (spawnSync('which', ['sg'], { stdio: 'ignore' }).status !== 0) return;
|
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`.'));
|
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'], {
|
const res = spawnSync('sg', ['docker', '-c', 'pnpm run setup:auto'], {
|
||||||
stdio: 'inherit',
|
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);
|
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
|
// Socket is unreachable due to group perms — current shell's supplementary
|
||||||
// groups are fixed at login, so `usermod -aG docker` (via install-docker.sh
|
// groups are fixed at login, so `usermod -aG docker` doesn't affect us
|
||||||
// or a prior install) doesn't affect us until next login. Re-exec this
|
// until next login. Ensure the user is in the docker group (install-docker.sh
|
||||||
// step under `sg docker` so the child picks up docker as its primary
|
// does this on fresh installs, but skips when Docker is already present),
|
||||||
// group and can talk to /var/run/docker.sock without a logout.
|
// 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')) {
|
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`');
|
log.info('Re-executing container step under `sg docker`');
|
||||||
const res = spawnSync(
|
const res = spawnSync(
|
||||||
'sg',
|
'sg',
|
||||||
|
|||||||
Reference in New Issue
Block a user