Skip to content

Commit bfd3177

Browse files
elitanclaude
andcommitted
Remove sudo requirement from setup command
- Update CLAUDE.md documentation to remove sudo prefix from setup examples - Add velo group check to doctor command for better diagnostics - Fix setup to remove old sudoers file before creating new one (prevents ownership issues) - Set proper ownership (root:root) for sudoers file - Update error messages to suggest setup without sudo prefix All tests passing after changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent d0ba9d2 commit bfd3177

File tree

4 files changed

+67
-15
lines changed

4 files changed

+67
-15
lines changed

CLAUDE.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
- never create markdown (`.md`) files after you are done. Never!
44
- never use emojis unless told to do so.
55
- i know i'm absolutly right. no need to tell me.
6-
- **NEVER use sudo when running velo commands** (except for the one-time `sudo velo setup` command). After setup, all commands run without sudo using ZFS delegation and Docker permissions.
6+
- **NEVER use sudo when running velo commands**. After setup, all commands run without sudo using ZFS delegation and Docker permissions.
77

88
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
99

@@ -30,16 +30,19 @@ bun install
3030
# Build the CLI
3131
bun run build
3232

33-
# Run directly (development)
33+
# Link for development (one-time setup)
34+
bun link
35+
36+
# After linking, just rebuild to update the binary
37+
bun run build # The symlink automatically points to updated dist/velo
38+
39+
# Run directly (development, without building)
3440
bun run src/index.ts
3541
# or
3642
bun run dev
3743

38-
# Install globally
39-
sudo cp dist/velo /usr/local/bin/
40-
4144
# Setup permissions (one-time, required before use)
42-
sudo velo setup
45+
velo setup
4346

4447
# Run tests (cleans up first, then runs in parallel)
4548
bun run test # Runs all tests via ./scripts/test.sh
@@ -239,7 +242,7 @@ Naming validation: Only `[a-zA-Z0-9_-]+` allowed for project/branch names
239242

240243
**One-time setup required:**
241244
```bash
242-
sudo velo setup
245+
velo setup
243246
```
244247

245248
This command:
@@ -329,7 +332,7 @@ When modifying branching logic:
329332
- Docker must be running with socket at `/var/run/docker.sock`
330333
- Bun runtime required (not Node.js)
331334
- ZFS pool must exist before running setup (auto-detected)
332-
- One-time permission setup required (`sudo velo setup`)
335+
- One-time permission setup required (`velo setup`)
333336
- Mount/unmount operations require sudo due to Linux kernel CAP_SYS_ADMIN requirement
334337
- Port allocation is dynamic via Docker (automatically assigns available ports)
335338
- Credentials stored in plain text in state.json (TODO: encrypt)

src/commands/doctor.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export async function doctorCommand() {
5959
await checkDockerInstalled(),
6060
await checkDockerRunning(),
6161
await checkDockerPermissions(),
62+
await checkVeloGroup(),
6263
await checkDockerImages(),
6364
];
6465
allResults.push(...dockerResults);
@@ -136,7 +137,7 @@ function printSummary(allResults: CheckResult[]) {
136137
console.log('✗ Issues detected. Please fix the failed checks above.');
137138
console.log();
138139
console.log('Common fixes:');
139-
console.log(` • Run setup: sudo ${CLI_NAME} setup`);
140+
console.log(` • Run setup: ${CLI_NAME} setup`);
140141
console.log(' • Create ZFS pool: sudo zpool create tank /dev/sdb');
141142
console.log(' • Log out and back in (for group changes)');
142143
} else if (warnings > 0) {
@@ -331,7 +332,7 @@ async function checkZFSPermissions(): Promise<CheckResult> {
331332
status: 'fail',
332333
message: 'ZFS permissions not configured',
333334
details: [
334-
`Run setup: sudo ${CLI_NAME} setup`,
335+
`Run setup: ${CLI_NAME} setup`,
335336
'This grants ZFS delegation permissions to your user',
336337
],
337338
};
@@ -474,6 +475,46 @@ async function checkDockerPermissions(): Promise<CheckResult> {
474475
}
475476
}
476477

478+
async function checkVeloGroup(): Promise<CheckResult> {
479+
try {
480+
// Skip check if running as root
481+
if (process.getuid && process.getuid() === 0) {
482+
return {
483+
name: `${CLI_NAME} Group`,
484+
status: 'warn',
485+
message: 'Running as root - group check skipped',
486+
};
487+
}
488+
489+
const groups = await $`groups`.text();
490+
const hasVeloGroup = groups.includes(CLI_NAME);
491+
492+
if (hasVeloGroup) {
493+
return {
494+
name: `${CLI_NAME} Group`,
495+
status: 'pass',
496+
message: `User in ${CLI_NAME} group`,
497+
};
498+
} else {
499+
return {
500+
name: `${CLI_NAME} Group`,
501+
status: 'fail',
502+
message: `Not in ${CLI_NAME} group`,
503+
details: [
504+
`Run setup: ${CLI_NAME} setup`,
505+
'Then log out and back in',
506+
],
507+
};
508+
}
509+
} catch (error) {
510+
return {
511+
name: `${CLI_NAME} Group`,
512+
status: 'fail',
513+
message: 'Unable to check group membership',
514+
};
515+
}
516+
}
517+
477518
async function checkDockerImages(): Promise<CheckResult> {
478519
try {
479520
const docker = new DockerManager();

src/commands/setup.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,14 @@ export async function setupCommand() {
182182
console.log(chalk.bold('[5/5]'), 'Installing sudoers configuration...');
183183

184184
try {
185+
// Remove old sudoers file if it exists (to avoid ownership issues)
186+
const sudoersPath = `/etc/sudoers.d/${CLI_NAME}`;
187+
try {
188+
await $`sudo rm -f ${sudoersPath}`.quiet();
189+
} catch (error) {
190+
// Ignore errors if file doesn't exist
191+
}
192+
185193
// Create group if needed
186194
try {
187195
await $`getent group ${CLI_NAME}`.quiet();
@@ -207,7 +215,6 @@ export async function setupCommand() {
207215
const homeDir = await $`getent passwd ${actualUser}`.text().then(s => s.trim().split(':')[5]);
208216

209217
// Create sudoers file
210-
const sudoersPath = `/etc/sudoers.d/${CLI_NAME}`;
211218
const sudoersContent = `# ${TOOL_NAME} - PostgreSQL database branching tool
212219
# This file grants minimal sudo permissions for required operations
213220
@@ -234,6 +241,7 @@ export async function setupCommand() {
234241
const tmpFile = `/tmp/${CLI_NAME}-sudoers-${Date.now()}`;
235242
await fs.writeFile(tmpFile, sudoersContent);
236243
await $`sudo mv ${tmpFile} ${sudoersPath}`;
244+
await $`sudo chown root:root ${sudoersPath}`;
237245
await $`sudo chmod 0440 ${sudoersPath}`;
238246

239247
// Verify sudoers syntax

src/utils/zfs-permissions.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,14 @@ export async function validateZFSPermissions(pool: string, datasetBase: string):
8787
console.error();
8888
console.error(chalk.bold('To fix this, run the one-time setup:'));
8989
console.error();
90-
console.error(chalk.cyan(` sudo ${CLI_NAME} setup`));
90+
console.error(chalk.cyan(` ${CLI_NAME} setup`));
9191
console.error();
9292
console.error(chalk.dim('This will grant the necessary ZFS permissions to your user account.'));
9393
console.error(chalk.dim(`Dataset: ${pool}/${datasetBase}`));
9494
console.error(chalk.dim(`User: ${username}`));
9595
console.error();
9696

97-
throw new Error(`ZFS permissions not configured. Run: sudo ${CLI_NAME} setup`);
97+
throw new Error(`ZFS permissions not configured. Run: ${CLI_NAME} setup`);
9898
}
9999
}
100100

@@ -134,13 +134,13 @@ export async function validateDockerPermissions(): Promise<void> {
134134
console.error();
135135
console.error(chalk.bold('To fix this, run the one-time setup:'));
136136
console.error();
137-
console.error(chalk.cyan(` sudo ${CLI_NAME} setup`));
137+
console.error(chalk.cyan(` ${CLI_NAME} setup`));
138138
console.error();
139139
console.error(chalk.dim('Then log out and log back in for the docker group to take effect.'));
140140
console.error(chalk.dim(`User: ${username}`));
141141
console.error();
142142

143-
throw new Error(`Docker permissions not configured. Run: sudo ${CLI_NAME} setup and re-login.`);
143+
throw new Error(`Docker permissions not configured. Run: ${CLI_NAME} setup and re-login.`);
144144
}
145145
}
146146

0 commit comments

Comments
 (0)