added ability to specify deploy key path

This commit is contained in:
Muhammad Ibrahim
2025-09-18 01:02:51 +01:00
parent c497c1db2a
commit 08f82bc795
5 changed files with 95 additions and 53 deletions

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "settings" ADD COLUMN "ssh_key_path" TEXT;

View File

@@ -180,6 +180,7 @@ model Settings {
updateInterval Int @map("update_interval") @default(60) // Update interval in minutes
autoUpdate Boolean @map("auto_update") @default(false) // Enable automatic agent updates
githubRepoUrl String @map("github_repo_url") @default("git@github.com:9technologygroup/patchmon.net.git") // GitHub repository URL for version checking
sshKeyPath String? @map("ssh_key_path") // Optional SSH key path for deploy key authentication
createdAt DateTime @map("created_at") @default(now())
updatedAt DateTime @map("updated_at") @updatedAt

View File

@@ -123,7 +123,8 @@ router.put('/', authenticateToken, requireManageSettings, [
body('frontendUrl').isLength({ min: 1 }).withMessage('Frontend URL is required'),
body('updateInterval').isInt({ min: 5, max: 1440 }).withMessage('Update interval must be between 5 and 1440 minutes'),
body('autoUpdate').isBoolean().withMessage('Auto update must be a boolean'),
body('githubRepoUrl').optional().isLength({ min: 1 }).withMessage('GitHub repo URL must be a non-empty string')
body('githubRepoUrl').optional().isLength({ min: 1 }).withMessage('GitHub repo URL must be a non-empty string'),
body('sshKeyPath').optional().isLength({ min: 1 }).withMessage('SSH key path must be a non-empty string')
], async (req, res) => {
try {
console.log('Settings update request body:', req.body);
@@ -133,8 +134,8 @@ router.put('/', authenticateToken, requireManageSettings, [
return res.status(400).json({ errors: errors.array() });
}
const { serverProtocol, serverHost, serverPort, frontendUrl, updateInterval, autoUpdate, githubRepoUrl } = req.body;
console.log('Extracted values:', { serverProtocol, serverHost, serverPort, frontendUrl, updateInterval, autoUpdate, githubRepoUrl });
const { serverProtocol, serverHost, serverPort, frontendUrl, updateInterval, autoUpdate, githubRepoUrl, sshKeyPath } = req.body;
console.log('Extracted values:', { serverProtocol, serverHost, serverPort, frontendUrl, updateInterval, autoUpdate, githubRepoUrl, sshKeyPath });
// Construct server URL from components
const serverUrl = `${serverProtocol}://${serverHost}:${serverPort}`;
@@ -165,7 +166,8 @@ router.put('/', authenticateToken, requireManageSettings, [
frontendUrl,
updateInterval: updateInterval || 60,
autoUpdate: autoUpdate || false,
githubRepoUrl: githubRepoUrl || 'git@github.com:9technologygroup/patchmon.net.git'
githubRepoUrl: githubRepoUrl || 'git@github.com:9technologygroup/patchmon.net.git',
sshKeyPath: sshKeyPath || null
}
});
console.log('Settings updated successfully:', settings);
@@ -186,7 +188,8 @@ router.put('/', authenticateToken, requireManageSettings, [
frontendUrl,
updateInterval: updateInterval || 60,
autoUpdate: autoUpdate || false,
githubRepoUrl: githubRepoUrl || 'git@github.com:9technologygroup/patchmon.net.git'
githubRepoUrl: githubRepoUrl || 'git@github.com:9technologygroup/patchmon.net.git',
sshKeyPath: sshKeyPath || null
}
});
}

View File

@@ -59,22 +59,56 @@ router.get('/check-updates', authenticateToken, requireManageSettings, async (re
return res.status(400).json({ error: 'Invalid GitHub repository URL format' });
}
// Use SSH to fetch latest tag from private repository
// Use SSH with deploy keys (secure approach)
const sshRepoUrl = `git@github.com:${owner}/${repo}.git`;
try {
// Set up environment for SSH
const sshKeyPath = process.env.HOME + '/.ssh/id_ed25519';
let sshKeyPath = null;
// First, try to use the configured SSH key path from settings
if (settings.sshKeyPath) {
try {
require('fs').accessSync(settings.sshKeyPath);
sshKeyPath = settings.sshKeyPath;
console.log(`Using configured SSH key at: ${sshKeyPath}`);
} catch (e) {
console.warn(`Configured SSH key path not accessible: ${settings.sshKeyPath}`);
}
}
// If no configured path or it's not accessible, try common locations
if (!sshKeyPath) {
const possibleKeyPaths = [
'/root/.ssh/id_ed25519', // Root user (if service runs as root)
'/root/.ssh/id_rsa', // Root user RSA key
'/home/patchmon/.ssh/id_ed25519', // PatchMon user
'/home/patchmon/.ssh/id_rsa', // PatchMon user RSA key
'/var/www/.ssh/id_ed25519', // Web user
'/var/www/.ssh/id_rsa' // Web user RSA key
];
for (const path of possibleKeyPaths) {
try {
require('fs').accessSync(path);
sshKeyPath = path;
console.log(`Found SSH key at: ${path}`);
break;
} catch (e) {
// Key not found at this path, try next
}
}
}
if (!sshKeyPath) {
throw new Error('No SSH deploy key found. Please configure the SSH key path in settings or ensure a deploy key is installed in one of the expected locations.');
}
const env = {
...process.env,
GIT_SSH_COMMAND: `ssh -i ${sshKeyPath} -o StrictHostKeyChecking=no`
GIT_SSH_COMMAND: `ssh -i ${sshKeyPath} -o StrictHostKeyChecking=no -o IdentitiesOnly=yes`
};
console.log('SSH Key Path:', sshKeyPath);
console.log('SSH Command:', env.GIT_SSH_COMMAND);
console.log('Repository URL:', sshRepoUrl);
// Fetch the latest tag using SSH with explicit key
// Fetch the latest tag using SSH with deploy key
const { stdout: latestTag } = await execAsync(
`git ls-remote --tags --sort=-version:refname ${sshRepoUrl} | head -n 1 | sed 's/.*refs\\/tags\\///' | sed 's/\\^{}//'`,
{
@@ -83,34 +117,12 @@ router.get('/check-updates', authenticateToken, requireManageSettings, async (re
}
);
console.log('Latest tag output:', latestTag);
const latestVersion = latestTag.trim().replace('v', ''); // Remove 'v' prefix
const currentVersion = '1.2.3';
// Simple version comparison (assumes semantic versioning)
const isUpdateAvailable = compareVersions(latestVersion, currentVersion) > 0;
// Get additional tag information
let tagInfo = {};
try {
const { stdout: tagDetails } = await execAsync(
`git ls-remote --tags ${sshRepoUrl} | grep "${latestTag.trim()}" | head -n 1`,
{
timeout: 5000,
env: env
}
);
// Extract commit hash and other details if needed
const parts = tagDetails.trim().split('\t');
if (parts.length >= 2) {
tagInfo.commitHash = parts[0];
}
} catch (tagDetailError) {
console.warn('Could not fetch tag details:', tagDetailError.message);
}
res.json({
currentVersion,
latestVersion,
@@ -118,34 +130,40 @@ router.get('/check-updates', authenticateToken, requireManageSettings, async (re
latestRelease: {
tagName: latestTag.trim(),
version: latestVersion,
commitHash: tagInfo.commitHash,
repository: `${owner}/${repo}`,
sshUrl: sshRepoUrl
sshUrl: sshRepoUrl,
sshKeyUsed: sshKeyPath
}
});
} catch (sshError) {
console.error('SSH Git error:', sshError.message);
// Check if it's a permission/access issue
if (sshError.message.includes('Permission denied') || sshError.message.includes('Host key verification failed')) {
return res.status(403).json({
return res.status(403).json({
error: 'SSH access denied to repository',
suggestion: 'Ensure your SSH key is properly configured and has access to the repository. Check your ~/.ssh/config and known_hosts.'
});
}
if (sshError.message.includes('not found') || sshError.message.includes('does not exist')) {
return res.status(404).json({
error: 'Repository not found',
suggestion: 'Check that the repository URL is correct and accessible.'
suggestion: 'Ensure your deploy key is properly configured and has access to the repository. Check that the key has read access to the repository.'
});
}
return res.status(500).json({
if (sshError.message.includes('not found') || sshError.message.includes('does not exist')) {
return res.status(404).json({
error: 'Repository not found',
suggestion: 'Check that the repository URL is correct and accessible with the deploy key.'
});
}
if (sshError.message.includes('No SSH deploy key found')) {
return res.status(400).json({
error: 'No SSH deploy key found',
suggestion: 'Please install a deploy key in one of the expected locations: /root/.ssh/, /home/patchmon/.ssh/, or /var/www/.ssh/'
});
}
return res.status(500).json({
error: 'Failed to fetch repository information',
details: sshError.message,
suggestion: 'Check SSH key configuration and repository access permissions.'
suggestion: 'Check deploy key configuration and repository access permissions.'
});
}

View File

@@ -11,7 +11,8 @@ const Settings = () => {
frontendUrl: 'http://localhost:3000',
updateInterval: 60,
autoUpdate: false,
githubRepoUrl: 'git@github.com:9technologygroup/patchmon.net.git'
githubRepoUrl: 'git@github.com:9technologygroup/patchmon.net.git',
sshKeyPath: ''
});
const [errors, setErrors] = useState({});
const [isDirty, setIsDirty] = useState(false);
@@ -66,7 +67,8 @@ const Settings = () => {
frontendUrl: settings.frontendUrl || 'http://localhost:3000',
updateInterval: settings.updateInterval || 60,
autoUpdate: settings.autoUpdate || false,
githubRepoUrl: settings.githubRepoUrl || 'git@github.com:9technologygroup/patchmon.net.git'
githubRepoUrl: settings.githubRepoUrl || 'git@github.com:9technologygroup/patchmon.net.git',
sshKeyPath: settings.sshKeyPath || ''
};
console.log('Setting form data to:', newFormData);
setFormData(newFormData);
@@ -722,6 +724,22 @@ const Settings = () => {
</p>
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
SSH Key Path (Optional)
</label>
<input
type="text"
value={formData.sshKeyPath || ''}
onChange={(e) => handleInputChange('sshKeyPath', e.target.value)}
className="w-full border border-secondary-300 dark:border-secondary-600 rounded-md shadow-sm focus:ring-primary-500 focus:border-primary-500 bg-white dark:bg-secondary-700 text-secondary-900 dark:text-white font-mono text-sm"
placeholder="/root/.ssh/id_ed25519"
/>
<p className="mt-1 text-xs text-secondary-500 dark:text-secondary-400">
Path to your SSH deploy key. Leave empty to auto-detect from common locations.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="bg-white dark:bg-secondary-800 rounded-lg p-4 border border-secondary-200 dark:border-secondary-600">
<div className="flex items-center gap-2 mb-2">