mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
feat: enhance project name sanitization and improve Docker image naming
- Added a `sanitizeProjectName` function to ensure project names are safe for shell commands and Docker image names by converting them to lowercase and removing non-alphanumeric characters. - Updated `dev.mjs` and `start.mjs` to utilize the new sanitization function when determining Docker image names, enhancing security and consistency. - Refactored the Docker entrypoint script to ensure proper permissions for the Cursor CLI config directory, improving setup reliability. - Clarified documentation regarding the storage location of OAuth tokens for the Cursor CLI on Linux. These changes improve the robustness of the Docker setup and enhance the overall development workflow.
This commit is contained in:
@@ -32,6 +32,53 @@ export function useCliStatus() {
|
||||
|
||||
const [isCheckingClaudeCli, setIsCheckingClaudeCli] = useState(false);
|
||||
|
||||
// Refresh Claude auth status from the server
|
||||
const refreshAuthStatus = useCallback(async () => {
|
||||
const api = getElectronAPI();
|
||||
if (!api?.setup?.getClaudeStatus) return;
|
||||
|
||||
try {
|
||||
const result = await api.setup.getClaudeStatus();
|
||||
if (result.success && result.auth) {
|
||||
// Cast to extended type that includes server-added fields
|
||||
const auth = result.auth as typeof result.auth & {
|
||||
oauthTokenValid?: boolean;
|
||||
apiKeyValid?: boolean;
|
||||
};
|
||||
// Map server method names to client method types
|
||||
// Server returns: oauth_token_env, oauth_token, api_key_env, api_key, credentials_file, cli_authenticated, none
|
||||
const validMethods = [
|
||||
'oauth_token_env',
|
||||
'oauth_token',
|
||||
'api_key',
|
||||
'api_key_env',
|
||||
'credentials_file',
|
||||
'cli_authenticated',
|
||||
'none',
|
||||
] as const;
|
||||
type AuthMethod = (typeof validMethods)[number];
|
||||
const method: AuthMethod = validMethods.includes(auth.method as AuthMethod)
|
||||
? (auth.method as AuthMethod)
|
||||
: auth.authenticated
|
||||
? 'api_key'
|
||||
: 'none'; // Default authenticated to api_key, not none
|
||||
const authStatus = {
|
||||
authenticated: auth.authenticated,
|
||||
method,
|
||||
hasCredentialsFile: auth.hasCredentialsFile ?? false,
|
||||
oauthTokenValid:
|
||||
auth.oauthTokenValid || auth.hasStoredOAuthToken || auth.hasEnvOAuthToken,
|
||||
apiKeyValid: auth.apiKeyValid || auth.hasStoredApiKey || auth.hasEnvApiKey,
|
||||
hasEnvOAuthToken: auth.hasEnvOAuthToken,
|
||||
hasEnvApiKey: auth.hasEnvApiKey,
|
||||
};
|
||||
setClaudeAuthStatus(authStatus);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to refresh Claude auth status:', error);
|
||||
}
|
||||
}, [setClaudeAuthStatus]);
|
||||
|
||||
// Check CLI status on mount
|
||||
useEffect(() => {
|
||||
const checkCliStatus = async () => {
|
||||
@@ -48,52 +95,11 @@ export function useCliStatus() {
|
||||
}
|
||||
|
||||
// Check Claude auth status (re-fetch on mount to ensure persistence)
|
||||
if (api?.setup?.getClaudeStatus) {
|
||||
try {
|
||||
const result = await api.setup.getClaudeStatus();
|
||||
if (result.success && result.auth) {
|
||||
// Cast to extended type that includes server-added fields
|
||||
const auth = result.auth as typeof result.auth & {
|
||||
oauthTokenValid?: boolean;
|
||||
apiKeyValid?: boolean;
|
||||
};
|
||||
// Map server method names to client method types
|
||||
// Server returns: oauth_token_env, oauth_token, api_key_env, api_key, credentials_file, cli_authenticated, none
|
||||
const validMethods = [
|
||||
'oauth_token_env',
|
||||
'oauth_token',
|
||||
'api_key',
|
||||
'api_key_env',
|
||||
'credentials_file',
|
||||
'cli_authenticated',
|
||||
'none',
|
||||
] as const;
|
||||
type AuthMethod = (typeof validMethods)[number];
|
||||
const method: AuthMethod = validMethods.includes(auth.method as AuthMethod)
|
||||
? (auth.method as AuthMethod)
|
||||
: auth.authenticated
|
||||
? 'api_key'
|
||||
: 'none'; // Default authenticated to api_key, not none
|
||||
const authStatus = {
|
||||
authenticated: auth.authenticated,
|
||||
method,
|
||||
hasCredentialsFile: auth.hasCredentialsFile ?? false,
|
||||
oauthTokenValid:
|
||||
auth.oauthTokenValid || auth.hasStoredOAuthToken || auth.hasEnvOAuthToken,
|
||||
apiKeyValid: auth.apiKeyValid || auth.hasStoredApiKey || auth.hasEnvApiKey,
|
||||
hasEnvOAuthToken: auth.hasEnvOAuthToken,
|
||||
hasEnvApiKey: auth.hasEnvApiKey,
|
||||
};
|
||||
setClaudeAuthStatus(authStatus);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to check Claude auth status:', error);
|
||||
}
|
||||
}
|
||||
await refreshAuthStatus();
|
||||
};
|
||||
|
||||
checkCliStatus();
|
||||
}, [setClaudeAuthStatus]);
|
||||
}, [refreshAuthStatus]);
|
||||
|
||||
// Refresh Claude CLI status and auth status
|
||||
const handleRefreshClaudeCli = useCallback(async () => {
|
||||
@@ -105,51 +111,13 @@ export function useCliStatus() {
|
||||
setClaudeCliStatus(status);
|
||||
}
|
||||
// Also refresh auth status
|
||||
if (api?.setup?.getClaudeStatus) {
|
||||
try {
|
||||
const result = await api.setup.getClaudeStatus();
|
||||
if (result.success && result.auth) {
|
||||
const auth = result.auth as typeof result.auth & {
|
||||
oauthTokenValid?: boolean;
|
||||
apiKeyValid?: boolean;
|
||||
};
|
||||
const validMethods = [
|
||||
'oauth_token_env',
|
||||
'oauth_token',
|
||||
'api_key',
|
||||
'api_key_env',
|
||||
'credentials_file',
|
||||
'cli_authenticated',
|
||||
'none',
|
||||
] as const;
|
||||
type AuthMethod = (typeof validMethods)[number];
|
||||
const method: AuthMethod = validMethods.includes(auth.method as AuthMethod)
|
||||
? (auth.method as AuthMethod)
|
||||
: auth.authenticated
|
||||
? 'api_key'
|
||||
: 'none';
|
||||
const authStatus = {
|
||||
authenticated: auth.authenticated,
|
||||
method,
|
||||
hasCredentialsFile: auth.hasCredentialsFile ?? false,
|
||||
oauthTokenValid:
|
||||
auth.oauthTokenValid || auth.hasStoredOAuthToken || auth.hasEnvOAuthToken,
|
||||
apiKeyValid: auth.apiKeyValid || auth.hasStoredApiKey || auth.hasEnvApiKey,
|
||||
hasEnvOAuthToken: auth.hasEnvOAuthToken,
|
||||
hasEnvApiKey: auth.hasEnvApiKey,
|
||||
};
|
||||
setClaudeAuthStatus(authStatus);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to refresh Claude auth status:', error);
|
||||
}
|
||||
}
|
||||
await refreshAuthStatus();
|
||||
} catch (error) {
|
||||
logger.error('Failed to refresh Claude CLI status:', error);
|
||||
} finally {
|
||||
setIsCheckingClaudeCli(false);
|
||||
}
|
||||
}, [setClaudeAuthStatus]);
|
||||
}, [refreshAuthStatus]);
|
||||
|
||||
return {
|
||||
claudeCliStatus,
|
||||
|
||||
40
dev.mjs
40
dev.mjs
@@ -47,6 +47,14 @@ const processes = {
|
||||
docker: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* Sanitize a project name to be safe for use in shell commands and Docker image names.
|
||||
* Converts to lowercase and removes any characters that aren't alphanumeric.
|
||||
*/
|
||||
function sanitizeProjectName(name) {
|
||||
return name.toLowerCase().replace(/[^a-z0-9]/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Docker images need to be rebuilt based on Dockerfile or package.json changes
|
||||
*/
|
||||
@@ -60,35 +68,27 @@ function shouldRebuildDockerImages() {
|
||||
const packageJsonMtime = statSync(packageJsonPath).mtimeMs;
|
||||
const latestSourceMtime = Math.max(dockerfileMtime, packageJsonMtime);
|
||||
|
||||
// Get image names from docker-compose config
|
||||
let serverImageName, uiImageName;
|
||||
// Get project name from docker-compose config, falling back to directory name
|
||||
let projectName;
|
||||
try {
|
||||
const composeConfig = execSync('docker compose config --format json', {
|
||||
encoding: 'utf-8',
|
||||
cwd: __dirname,
|
||||
});
|
||||
const config = JSON.parse(composeConfig);
|
||||
|
||||
// Docker Compose generates image names as <project>_<service>
|
||||
// Get project name from config or default to directory name
|
||||
const projectName =
|
||||
config.name ||
|
||||
path
|
||||
.basename(__dirname)
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]/g, '');
|
||||
serverImageName = `${projectName}_server`;
|
||||
uiImageName = `${projectName}_ui`;
|
||||
projectName = config.name;
|
||||
} catch (error) {
|
||||
// Fallback to default naming convention
|
||||
const projectName = path
|
||||
.basename(__dirname)
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]/g, '');
|
||||
serverImageName = `${projectName}_server`;
|
||||
uiImageName = `${projectName}_ui`;
|
||||
// Fallback handled below
|
||||
}
|
||||
|
||||
// Sanitize project name (whether from config or fallback)
|
||||
// This prevents command injection and ensures valid Docker image names
|
||||
const sanitizedProjectName = sanitizeProjectName(
|
||||
projectName || path.basename(__dirname)
|
||||
);
|
||||
const serverImageName = `${sanitizedProjectName}_server`;
|
||||
const uiImageName = `${sanitizedProjectName}_ui`;
|
||||
|
||||
// Check if images exist and get their creation times
|
||||
let needsRebuild = false;
|
||||
|
||||
|
||||
@@ -17,19 +17,13 @@ fi
|
||||
chown -R automaker:automaker /home/automaker/.claude
|
||||
chmod 700 /home/automaker/.claude
|
||||
|
||||
# Fix permissions on Cursor CLI config directory if it exists
|
||||
# This handles the case where a volume is mounted and owned by root
|
||||
if [ -d "/home/automaker/.cursor" ]; then
|
||||
chown -R automaker:automaker /home/automaker/.cursor
|
||||
chmod -R 700 /home/automaker/.cursor
|
||||
fi
|
||||
|
||||
# Ensure the directory exists with correct permissions if volume is empty
|
||||
# Ensure Cursor CLI config directory exists with correct permissions
|
||||
# This handles both: mounted volumes (owned by root) and empty directories
|
||||
if [ ! -d "/home/automaker/.cursor" ]; then
|
||||
mkdir -p /home/automaker/.cursor
|
||||
chown automaker:automaker /home/automaker/.cursor
|
||||
chmod 700 /home/automaker/.cursor
|
||||
fi
|
||||
chown -R automaker:automaker /home/automaker/.cursor
|
||||
chmod -R 700 /home/automaker/.cursor
|
||||
|
||||
# If CURSOR_AUTH_TOKEN is set, write it to the cursor auth file
|
||||
# On Linux, cursor-agent uses ~/.config/cursor/auth.json for file-based credential storage
|
||||
|
||||
@@ -78,7 +78,7 @@ echo "CURSOR_AUTH_TOKEN=$(./scripts/get-cursor-token.sh)" >> .env
|
||||
**Note**: The cursor-agent CLI stores its OAuth tokens separately from the Cursor IDE:
|
||||
|
||||
- **macOS**: Tokens are stored in Keychain (service: `cursor-access-token`)
|
||||
- **Linux**: Tokens are stored in `~/.config/cursor/auth.json`
|
||||
- **Linux**: Tokens are stored in `~/.config/cursor/auth.json` (not `~/.cursor`)
|
||||
|
||||
### Apply to container
|
||||
|
||||
|
||||
40
start.mjs
40
start.mjs
@@ -56,6 +56,14 @@ const processes = {
|
||||
docker: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* Sanitize a project name to be safe for use in shell commands and Docker image names.
|
||||
* Converts to lowercase and removes any characters that aren't alphanumeric.
|
||||
*/
|
||||
function sanitizeProjectName(name) {
|
||||
return name.toLowerCase().replace(/[^a-z0-9]/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Docker images need to be rebuilt based on Dockerfile or package.json changes
|
||||
*/
|
||||
@@ -69,35 +77,27 @@ function shouldRebuildDockerImages() {
|
||||
const packageJsonMtime = statSync(packageJsonPath).mtimeMs;
|
||||
const latestSourceMtime = Math.max(dockerfileMtime, packageJsonMtime);
|
||||
|
||||
// Get image names from docker-compose config
|
||||
let serverImageName, uiImageName;
|
||||
// Get project name from docker-compose config, falling back to directory name
|
||||
let projectName;
|
||||
try {
|
||||
const composeConfig = execSync('docker compose config --format json', {
|
||||
encoding: 'utf-8',
|
||||
cwd: __dirname,
|
||||
});
|
||||
const config = JSON.parse(composeConfig);
|
||||
|
||||
// Docker Compose generates image names as <project>_<service>
|
||||
// Get project name from config or default to directory name
|
||||
const projectName =
|
||||
config.name ||
|
||||
path
|
||||
.basename(__dirname)
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]/g, '');
|
||||
serverImageName = `${projectName}_server`;
|
||||
uiImageName = `${projectName}_ui`;
|
||||
projectName = config.name;
|
||||
} catch (error) {
|
||||
// Fallback to default naming convention
|
||||
const projectName = path
|
||||
.basename(__dirname)
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]/g, '');
|
||||
serverImageName = `${projectName}_server`;
|
||||
uiImageName = `${projectName}_ui`;
|
||||
// Fallback handled below
|
||||
}
|
||||
|
||||
// Sanitize project name (whether from config or fallback)
|
||||
// This prevents command injection and ensures valid Docker image names
|
||||
const sanitizedProjectName = sanitizeProjectName(
|
||||
projectName || path.basename(__dirname)
|
||||
);
|
||||
const serverImageName = `${sanitizedProjectName}_server`;
|
||||
const uiImageName = `${sanitizedProjectName}_ui`;
|
||||
|
||||
// Check if images exist and get their creation times
|
||||
let needsRebuild = false;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user