Update PatchMon version to 1.2.7

- Updated agent script version to 1.2.7
- Updated all package.json files to version 1.2.7
- Updated backend version references
- Updated setup script version references
- Fixed agent file path issues in API endpoints
- Fixed linting issues (Node.js imports, unused variables, accessibility)
- Created comprehensive version update guide in patchmon-admin/READMEs/
This commit is contained in:
Muhammad Ibrahim
2025-09-29 20:42:14 +01:00
parent 49c02a54dc
commit b49ea6b197
20 changed files with 1197 additions and 1238 deletions

View File

@@ -37,7 +37,7 @@ function createPrismaClient() {
},
},
log:
process.env.NODE_ENV === "development"
process.env.PRISMA_LOG_QUERIES === "true"
? ["query", "info", "warn", "error"]
: ["warn", "error"],
errorFormat: "pretty",
@@ -66,17 +66,21 @@ async function waitForDatabase(prisma, options = {}) {
parseInt(process.env.PM_DB_CONN_WAIT_INTERVAL, 10) ||
2;
console.log(
`Waiting for database connection (max ${maxAttempts} attempts, ${waitInterval}s interval)...`,
);
if (process.env.ENABLE_LOGGING === "true") {
console.log(
`Waiting for database connection (max ${maxAttempts} attempts, ${waitInterval}s interval)...`,
);
}
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
const isConnected = await checkDatabaseConnection(prisma);
if (isConnected) {
console.log(
`Database connected successfully after ${attempt} attempt(s)`,
);
if (process.env.ENABLE_LOGGING === "true") {
console.log(
`Database connected successfully after ${attempt} attempt(s)`,
);
}
return true;
}
} catch {
@@ -84,9 +88,11 @@ async function waitForDatabase(prisma, options = {}) {
}
if (attempt < maxAttempts) {
console.log(
`⏳ Database not ready (attempt ${attempt}/${maxAttempts}), retrying in ${waitInterval}s...`,
);
if (process.env.ENABLE_LOGGING === "true") {
console.log(
`⏳ Database not ready (attempt ${attempt}/${maxAttempts}), retrying in ${waitInterval}s...`,
);
}
await new Promise((resolve) => setTimeout(resolve, waitInterval * 1000));
}
}

View File

@@ -3,8 +3,8 @@ const { PrismaClient } = require("@prisma/client");
const { body, validationResult } = require("express-validator");
const { v4: uuidv4 } = require("uuid");
const crypto = require("node:crypto");
const path = require("node:path");
const fs = require("node:fs");
const _path = require("node:path");
const _fs = require("node:fs");
const { authenticateToken, _requireAdmin } = require("../middleware/auth");
const {
requireManageHosts,
@@ -14,72 +14,48 @@ const {
const router = express.Router();
const prisma = new PrismaClient();
// Public endpoint to download the agent script
// Secure endpoint to download the agent script (requires API authentication)
router.get("/agent/download", async (req, res) => {
try {
const { version } = req.query;
// Verify API credentials
const apiId = req.headers["x-api-id"];
const apiKey = req.headers["x-api-key"];
let agentVersion;
if (version) {
// Download specific version
agentVersion = await prisma.agent_versions.findUnique({
where: { version },
});
if (!agentVersion) {
return res.status(404).json({ error: "Agent version not found" });
}
} else {
// Download current version (latest)
agentVersion = await prisma.agent_versions.findFirst({
where: { is_current: true },
orderBy: { created_at: "desc" },
});
if (!agentVersion) {
// Fallback to default version
agentVersion = await prisma.agent_versions.findFirst({
where: { is_default: true },
orderBy: { created_at: "desc" },
});
}
if (!apiId || !apiKey) {
return res.status(401).json({ error: "API credentials required" });
}
// Use script content from database if available, otherwise fallback to file
if (agentVersion?.script_content) {
// Convert Windows line endings to Unix line endings
const scriptContent = agentVersion.script_content
.replace(/\r\n/g, "\n")
.replace(/\r/g, "\n");
res.setHeader("Content-Type", "application/x-shellscript");
res.setHeader(
"Content-Disposition",
`attachment; filename="patchmon-agent-${agentVersion.version}.sh"`,
);
res.send(scriptContent);
} else {
// Fallback to file system when no database version exists or script has no content
const agentPath = path.join(
__dirname,
"../../../agents/patchmon-agent.sh",
);
if (!fs.existsSync(agentPath)) {
return res.status(404).json({ error: "Agent script not found" });
}
// Read file and convert line endings
const scriptContent = fs
.readFileSync(agentPath, "utf8")
.replace(/\r\n/g, "\n")
.replace(/\r/g, "\n");
res.setHeader("Content-Type", "application/x-shellscript");
const version = agentVersion ? `-${agentVersion.version}` : "";
res.setHeader(
"Content-Disposition",
`attachment; filename="patchmon-agent${version}.sh"`,
);
res.send(scriptContent);
// Validate API credentials
const host = await prisma.hosts.findUnique({
where: { api_id: apiId },
});
if (!host || host.api_key !== apiKey) {
return res.status(401).json({ error: "Invalid API credentials" });
}
// Serve agent script directly from file system
const fs = require("node:fs");
const path = require("node:path");
const agentPath = path.join(__dirname, "../../../agents/patchmon-agent.sh");
if (!fs.existsSync(agentPath)) {
return res.status(404).json({ error: "Agent script not found" });
}
// Read file and convert line endings
const scriptContent = fs
.readFileSync(agentPath, "utf8")
.replace(/\r\n/g, "\n")
.replace(/\r/g, "\n");
res.setHeader("Content-Type", "application/x-shellscript");
res.setHeader(
"Content-Disposition",
'attachment; filename="patchmon-agent.sh"',
);
res.send(scriptContent);
} catch (error) {
console.error("Agent download error:", error);
res.status(500).json({ error: "Failed to download agent script" });
@@ -89,21 +65,32 @@ router.get("/agent/download", async (req, res) => {
// Version check endpoint for agents
router.get("/agent/version", async (_req, res) => {
try {
const currentVersion = await prisma.agent_versions.findFirst({
where: { is_current: true },
orderBy: { created_at: "desc" },
});
const fs = require("node:fs");
const path = require("node:path");
if (!currentVersion) {
return res.status(404).json({ error: "No current agent version found" });
// Read version directly from agent script file
const agentPath = path.join(__dirname, "../../../agents/patchmon-agent.sh");
if (!fs.existsSync(agentPath)) {
return res.status(404).json({ error: "Agent script not found" });
}
const scriptContent = fs.readFileSync(agentPath, "utf8");
const versionMatch = scriptContent.match(/AGENT_VERSION="([^"]+)"/);
if (!versionMatch) {
return res
.status(500)
.json({ error: "Could not extract version from agent script" });
}
const currentVersion = versionMatch[1];
res.json({
currentVersion: currentVersion.version,
downloadUrl:
currentVersion.download_url || `/api/v1/hosts/agent/download`,
releaseNotes: currentVersion.release_notes,
minServerVersion: currentVersion.min_server_version,
currentVersion: currentVersion,
downloadUrl: `/api/v1/hosts/agent/download`,
releaseNotes: `PatchMon Agent v${currentVersion}`,
minServerVersion: null,
});
} catch (error) {
console.error("Version check error:", error);
@@ -527,42 +514,7 @@ router.post(
});
});
// Check if agent auto-update is enabled and if there's a newer version available
let autoUpdateResponse = null;
try {
const settings = await prisma.settings.findFirst();
// Check both global agent auto-update setting AND host-specific agent auto-update setting
if (settings?.auto_update && host.auto_update) {
// Get current agent version from the request
const currentAgentVersion = req.body.agentVersion;
if (currentAgentVersion) {
// Get the latest agent version
const latestAgentVersion = await prisma.agent_versions.findFirst({
where: { is_current: true },
orderBy: { created_at: "desc" },
});
if (
latestAgentVersion &&
latestAgentVersion.version !== currentAgentVersion
) {
// There's a newer version available
autoUpdateResponse = {
shouldUpdate: true,
currentVersion: currentAgentVersion,
latestVersion: latestAgentVersion.version,
message:
"A newer agent version is available. Run: /usr/local/bin/patchmon-agent.sh update-agent",
updateCommand: "update-agent",
};
}
}
}
} catch (error) {
console.error("Agent auto-update check error:", error);
// Don't fail the update if agent auto-update check fails
}
// Agent auto-update is now handled client-side by the agent itself
const response = {
message: "Host updated successfully",
@@ -571,11 +523,6 @@ router.post(
securityUpdates: securityCount,
};
// Add agent auto-update response if available
if (autoUpdateResponse) {
response.autoUpdate = autoUpdateResponse;
}
// Check if crontab update is needed (when update interval changes)
// This is a simple check - if the host has auto-update enabled, we'll suggest crontab update
if (host.auto_update) {
@@ -1103,9 +1050,26 @@ router.patch(
},
);
// Serve the installation script
router.get("/install", async (_req, res) => {
// Serve the installation script (requires API authentication)
router.get("/install", async (req, res) => {
try {
// Verify API credentials
const apiId = req.headers["x-api-id"];
const apiKey = req.headers["x-api-key"];
if (!apiId || !apiKey) {
return res.status(401).json({ error: "API credentials required" });
}
// Validate API credentials
const host = await prisma.hosts.findUnique({
where: { api_id: apiId },
});
if (!host || host.api_key !== apiKey) {
return res.status(401).json({ error: "Invalid API credentials" });
}
const fs = require("node:fs");
const path = require("node:path");
@@ -1124,14 +1088,11 @@ router.get("/install", async (_req, res) => {
script = script.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
// Get the configured server URL from settings
let serverUrl = "http://localhost:3001";
try {
const settings = await prisma.settings.findFirst();
if (settings) {
// Replace the default server URL in the script with the configured one
script = script.replace(
/PATCHMON_URL="[^"]*"/g,
`PATCHMON_URL="${settings.server_url}"`,
);
if (settings?.server_url) {
serverUrl = settings.server_url;
}
} catch (settingsError) {
console.warn(
@@ -1140,6 +1101,18 @@ router.get("/install", async (_req, res) => {
);
}
// Inject the API credentials and server URL into the script as environment variables
const envVars = `#!/bin/bash
export PATCHMON_URL="${serverUrl}"
export API_ID="${host.api_id}"
export API_KEY="${host.api_key}"
`;
// Remove the shebang from the original script and prepend our env vars
script = script.replace(/^#!/, "#");
script = envVars + script;
res.setHeader("Content-Type", "text/plain");
res.setHeader(
"Content-Disposition",
@@ -1152,215 +1125,247 @@ router.get("/install", async (_req, res) => {
}
});
// ==================== AGENT VERSION MANAGEMENT ====================
// Serve the removal script (public endpoint - no authentication required)
router.get("/remove", async (_req, res) => {
try {
const fs = require("node:fs");
const path = require("node:path");
// Get all agent versions (admin only)
const scriptPath = path.join(
__dirname,
"../../../agents/patchmon_remove.sh",
);
if (!fs.existsSync(scriptPath)) {
return res.status(404).json({ error: "Removal script not found" });
}
// Read the script content
const script = fs.readFileSync(scriptPath, "utf8");
// Set appropriate headers for script download
res.setHeader("Content-Type", "text/plain");
res.setHeader(
"Content-Disposition",
'inline; filename="patchmon_remove.sh"',
);
res.send(script);
} catch (error) {
console.error("Removal script error:", error);
res.status(500).json({ error: "Failed to serve removal script" });
}
});
// ==================== AGENT FILE MANAGEMENT ====================
// Get agent file information (admin only)
router.get(
"/agent/versions",
"/agent/info",
authenticateToken,
requireManageSettings,
async (_req, res) => {
try {
const versions = await prisma.agent_versions.findMany({
orderBy: { created_at: "desc" },
});
const fs = require("node:fs").promises;
const path = require("node:path");
res.json(versions);
} catch (error) {
console.error("Get agent versions error:", error);
res.status(500).json({ error: "Failed to get agent versions" });
}
},
);
const agentPath = path.join(
__dirname,
"../../../agents/patchmon-agent.sh",
);
// Create new agent version (admin only)
router.post(
"/agent/versions",
authenticateToken,
requireManageSettings,
[
body("version").isLength({ min: 1 }).withMessage("Version is required"),
body("releaseNotes").optional().isString(),
body("downloadUrl")
.optional()
.isURL()
.withMessage("Download URL must be valid"),
body("minServerVersion").optional().isString(),
body("scriptContent").optional().isString(),
body("isDefault").optional().isBoolean(),
],
async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
const stats = await fs.stat(agentPath);
const content = await fs.readFile(agentPath, "utf8");
const {
version,
releaseNotes,
downloadUrl,
minServerVersion,
scriptContent,
isDefault,
} = req.body;
// Extract version from agent script (look for AGENT_VERSION= line)
const versionMatch = content.match(/^AGENT_VERSION="([^"]+)"/m);
const version = versionMatch ? versionMatch[1] : "unknown";
// Check if version already exists
const existingVersion = await prisma.agent_versions.findUnique({
where: { version },
});
if (existingVersion) {
return res.status(400).json({ error: "Version already exists" });
}
// If this is being set as default, unset other defaults
if (isDefault) {
await prisma.agent_versions.updateMany({
where: { is_default: true },
data: {
is_default: false,
updated_at: new Date(),
},
});
}
const agentVersion = await prisma.agent_versions.create({
data: {
id: uuidv4(),
res.json({
exists: true,
version,
release_notes: releaseNotes,
download_url: downloadUrl,
min_server_version: minServerVersion,
script_content: scriptContent,
is_default: isDefault || false,
is_current: false,
updated_at: new Date(),
},
});
res.status(201).json(agentVersion);
lastModified: stats.mtime,
size: stats.size,
sizeFormatted: `${(stats.size / 1024).toFixed(1)} KB`,
});
} catch (error) {
if (error.code === "ENOENT") {
res.json({
exists: false,
version: null,
lastModified: null,
size: 0,
sizeFormatted: "0 KB",
});
} else {
throw error;
}
}
} catch (error) {
console.error("Create agent version error:", error);
res.status(500).json({ error: "Failed to create agent version" });
console.error("Get agent info error:", error);
res.status(500).json({ error: "Failed to get agent information" });
}
},
);
// Set current agent version (admin only)
router.patch(
"/agent/versions/:versionId/current",
// Update agent file (admin only)
router.post(
"/agent/upload",
authenticateToken,
requireManageSettings,
async (req, res) => {
try {
const { versionId } = req.params;
const { scriptContent } = req.body;
// First, unset all current versions
await prisma.agent_versions.updateMany({
where: { is_current: true },
data: { is_current: false, updated_at: new Date() },
});
if (!scriptContent || typeof scriptContent !== "string") {
return res.status(400).json({ error: "Script content is required" });
}
// Set the specified version as current
const agentVersion = await prisma.agent_versions.update({
where: { id: versionId },
data: { is_current: true, updated_at: new Date() },
});
res.json(agentVersion);
} catch (error) {
console.error("Set current agent version error:", error);
res.status(500).json({ error: "Failed to set current agent version" });
}
},
);
// Set default agent version (admin only)
router.patch(
"/agent/versions/:versionId/default",
authenticateToken,
requireManageSettings,
async (req, res) => {
try {
const { versionId } = req.params;
// First, unset all default versions
await prisma.agent_versions.updateMany({
where: { is_default: true },
data: { is_default: false, updated_at: new Date() },
});
// Set the specified version as default
const agentVersion = await prisma.agent_versions.update({
where: { id: versionId },
data: { is_default: true, updated_at: new Date() },
});
res.json(agentVersion);
} catch (error) {
console.error("Set default agent version error:", error);
res.status(500).json({ error: "Failed to set default agent version" });
}
},
);
// Delete agent version (admin only)
router.delete(
"/agent/versions/:versionId",
authenticateToken,
requireManageSettings,
async (req, res) => {
try {
const { versionId } = req.params;
// Validate versionId format
if (!versionId || versionId.length < 10) {
// Basic validation - check if it looks like a shell script
if (!scriptContent.trim().startsWith("#!/")) {
return res.status(400).json({
error: "Invalid agent version ID format",
details: "The provided ID does not match expected format",
error: "Invalid script format - must start with shebang (#!/...)",
});
}
const agentVersion = await prisma.agent_versions.findUnique({
where: { id: versionId },
});
const fs = require("node:fs").promises;
const path = require("node:path");
if (!agentVersion) {
return res.status(404).json({
error: "Agent version not found",
details: `No agent version found with ID: ${versionId}`,
suggestion:
"Please refresh the page to get the latest agent versions",
});
const agentPath = path.join(
__dirname,
"../../../agents/patchmon-agent.sh",
);
// Create backup of existing file
try {
const backupPath = `${agentPath}.backup.${Date.now()}`;
await fs.copyFile(agentPath, backupPath);
console.log(`Created backup: ${backupPath}`);
} catch (error) {
// Ignore if original doesn't exist
if (error.code !== "ENOENT") {
console.warn("Failed to create backup:", error.message);
}
}
if (agentVersion.is_current) {
return res.status(400).json({
error: "Cannot delete current agent version",
details: `Version ${agentVersion.version} is currently active`,
suggestion: "Set another version as current before deleting this one",
});
}
// Write new agent script
await fs.writeFile(agentPath, scriptContent, { mode: 0o755 });
await prisma.agent_versions.delete({
where: { id: versionId },
});
// Get updated file info
const stats = await fs.stat(agentPath);
const versionMatch = scriptContent.match(/^AGENT_VERSION="([^"]+)"/m);
const version = versionMatch ? versionMatch[1] : "unknown";
res.json({
message: "Agent version deleted successfully",
deletedVersion: agentVersion.version,
message: "Agent script updated successfully",
version,
lastModified: stats.mtime,
size: stats.size,
sizeFormatted: `${(stats.size / 1024).toFixed(1)} KB`,
});
} catch (error) {
console.error("Delete agent version error:", error);
res.status(500).json({
error: "Failed to delete agent version",
details: error.message,
});
console.error("Upload agent error:", error);
res.status(500).json({ error: "Failed to update agent script" });
}
},
);
// Get agent file timestamp for update checking (requires API credentials)
router.get("/agent/timestamp", async (req, res) => {
try {
// Check for API credentials
const apiId = req.headers["x-api-id"];
const apiKey = req.headers["x-api-key"];
if (!apiId || !apiKey) {
return res.status(401).json({ error: "API credentials required" });
}
// Verify API credentials
const host = await prisma.hosts.findFirst({
where: {
api_id: apiId,
api_key: apiKey,
},
});
if (!host) {
return res.status(401).json({ error: "Invalid API credentials" });
}
const fs = require("node:fs").promises;
const path = require("node:path");
const agentPath = path.join(__dirname, "../../../agents/patchmon-agent.sh");
try {
const stats = await fs.stat(agentPath);
const content = await fs.readFile(agentPath, "utf8");
// Extract version from agent script
const versionMatch = content.match(/^AGENT_VERSION="([^"]+)"/m);
const version = versionMatch ? versionMatch[1] : "unknown";
res.json({
version,
lastModified: stats.mtime,
timestamp: Math.floor(stats.mtime.getTime() / 1000), // Unix timestamp
exists: true,
});
} catch (error) {
if (error.code === "ENOENT") {
res.json({
version: null,
lastModified: null,
timestamp: 0,
exists: false,
});
} else {
throw error;
}
}
} catch (error) {
console.error("Get agent timestamp error:", error);
res.status(500).json({ error: "Failed to get agent timestamp" });
}
});
// Get settings for agent (requires API credentials)
router.get("/settings", async (req, res) => {
try {
// Check for API credentials
const apiId = req.headers["x-api-id"];
const apiKey = req.headers["x-api-key"];
if (!apiId || !apiKey) {
return res.status(401).json({ error: "API credentials required" });
}
// Verify API credentials
const host = await prisma.hosts.findFirst({
where: {
api_id: apiId,
api_key: apiKey,
},
});
if (!host) {
return res.status(401).json({ error: "Invalid API credentials" });
}
const settings = await prisma.settings.findFirst();
// Return both global and host-specific auto-update settings
res.json({
auto_update: settings?.auto_update || false,
host_auto_update: host.auto_update || false,
});
} catch (error) {
console.error("Get settings error:", error);
res.status(500).json({ error: "Failed to get settings" });
}
});
// Update host friendly name (admin only)
router.patch(
"/:hostId/friendly-name",

View File

@@ -109,7 +109,9 @@ async function triggerCrontabUpdates() {
router.get("/", authenticateToken, requireManageSettings, async (_req, res) => {
try {
const settings = await getSettings();
console.log("Returning settings:", settings);
if (process.env.ENABLE_LOGGING === "true") {
console.log("Returning settings:", settings);
}
res.json(settings);
} catch (error) {
console.error("Settings fetch error:", error);
@@ -239,9 +241,26 @@ router.get("/server-url", async (_req, res) => {
}
});
// Get update interval policy for agents (public endpoint)
router.get("/update-interval", async (_req, res) => {
// Get update interval policy for agents (requires API authentication)
router.get("/update-interval", async (req, res) => {
try {
// Verify API credentials
const apiId = req.headers["x-api-id"];
const apiKey = req.headers["x-api-key"];
if (!apiId || !apiKey) {
return res.status(401).json({ error: "API credentials required" });
}
// Validate API credentials
const host = await prisma.hosts.findUnique({
where: { api_id: apiId },
});
if (!host || host.api_key !== apiKey) {
return res.status(401).json({ error: "Invalid API credentials" });
}
const settings = await getSettings();
res.json({
updateInterval: settings.update_interval,

View File

@@ -14,7 +14,7 @@ const router = express.Router();
router.get("/current", authenticateToken, async (_req, res) => {
try {
// Read version from package.json dynamically
let currentVersion = "1.2.6"; // fallback
let currentVersion = "1.2.7"; // fallback
try {
const packageJson = require("../../package.json");
@@ -174,7 +174,7 @@ router.get(
return res.status(400).json({ error: "Settings not found" });
}
const currentVersion = "1.2.6";
const currentVersion = "1.2.7";
const latestVersion = settings.latest_version || currentVersion;
const isUpdateAvailable = settings.update_available || false;
const lastUpdateCheck = settings.last_update_check || null;

View File

@@ -30,200 +30,6 @@ const { initSettings } = require("./services/settingsService");
// Initialize Prisma client with optimized connection pooling for multiple instances
const prisma = createPrismaClient();
// Simple version comparison function for semantic versioning
function compareVersions(version1, version2) {
const v1Parts = version1.split(".").map(Number);
const v2Parts = version2.split(".").map(Number);
// Ensure both arrays have the same length
const maxLength = Math.max(v1Parts.length, v2Parts.length);
while (v1Parts.length < maxLength) v1Parts.push(0);
while (v2Parts.length < maxLength) v2Parts.push(0);
for (let i = 0; i < maxLength; i++) {
if (v1Parts[i] > v2Parts[i]) return true;
if (v1Parts[i] < v2Parts[i]) return false;
}
return false; // versions are equal
}
// Function to check and import agent version on startup
async function checkAndImportAgentVersion() {
console.log("🔍 Starting agent version auto-import check...");
// Skip if auto-import is disabled
if (process.env.AUTO_IMPORT_AGENT_VERSION === "false") {
console.log("❌ Auto-import of agent version is disabled");
if (process.env.ENABLE_LOGGING === "true") {
logger.info("Auto-import of agent version is disabled");
}
return;
}
try {
const fs = require("node:fs");
const path = require("node:path");
const crypto = require("node:crypto");
// Read and validate agent script
const agentScriptPath = path.join(
__dirname,
"../../agents/patchmon-agent.sh",
);
console.log("📁 Agent script path:", agentScriptPath);
// Check if file exists
if (!fs.existsSync(agentScriptPath)) {
console.log("❌ Agent script file not found, skipping version check");
if (process.env.ENABLE_LOGGING === "true") {
logger.warn("Agent script file not found, skipping version check");
}
return;
}
console.log("✅ Agent script file found");
// Read the file content
const scriptContent = fs.readFileSync(agentScriptPath, "utf8");
// Extract version from script content
const versionMatch = scriptContent.match(/AGENT_VERSION="([^"]+)"/);
if (!versionMatch) {
console.log(
"❌ Could not extract version from agent script, skipping version check",
);
if (process.env.ENABLE_LOGGING === "true") {
logger.warn(
"Could not extract version from agent script, skipping version check",
);
}
return;
}
const localVersion = versionMatch[1];
console.log("📋 Local version:", localVersion);
// Check if this version already exists in database
const existingVersion = await prisma.agent_versions.findUnique({
where: { version: localVersion },
});
if (existingVersion) {
console.log(
`✅ Agent version ${localVersion} already exists in database`,
);
if (process.env.ENABLE_LOGGING === "true") {
logger.info(`Agent version ${localVersion} already exists in database`);
}
return;
}
console.log(`🆕 Agent version ${localVersion} not found in database`);
// Get existing versions for comparison
const allVersions = await prisma.agent_versions.findMany({
select: { version: true },
orderBy: { created_at: "desc" },
});
// Determine version flags and whether to proceed
const isFirstVersion = allVersions.length === 0;
const isNewerVersion =
!isFirstVersion && compareVersions(localVersion, allVersions[0].version);
if (!isFirstVersion && !isNewerVersion) {
console.log(
`❌ Agent version ${localVersion} is not newer than existing versions, skipping import`,
);
if (process.env.ENABLE_LOGGING === "true") {
logger.info(
`Agent version ${localVersion} is not newer than existing versions, skipping import`,
);
}
return;
}
const shouldSetAsCurrent = isFirstVersion || isNewerVersion;
const shouldSetAsDefault = isFirstVersion;
console.log(
isFirstVersion
? `📊 No existing versions found in database`
: `📊 Found ${allVersions.length} existing versions in database, latest: ${allVersions[0].version}`,
);
if (!isFirstVersion) {
console.log(
`🔄 Version comparison: ${localVersion} > ${allVersions[0].version} = ${isNewerVersion}`,
);
}
// Clear existing flags if needed
const updatePromises = [];
if (shouldSetAsCurrent) {
updatePromises.push(
prisma.agent_versions.updateMany({
where: { is_current: true },
data: { is_current: false },
}),
);
}
if (shouldSetAsDefault) {
updatePromises.push(
prisma.agent_versions.updateMany({
where: { is_default: true },
data: { is_default: false },
}),
);
}
if (updatePromises.length > 0) {
await Promise.all(updatePromises);
}
// Create new version
await prisma.agent_versions.create({
data: {
id: crypto.randomUUID(),
version: localVersion,
release_notes: `Auto-imported on startup (${new Date().toISOString()})`,
script_content: scriptContent,
is_default: shouldSetAsDefault,
is_current: shouldSetAsCurrent,
updated_at: new Date(),
},
});
console.log(
`🎉 Successfully auto-imported new agent version ${localVersion} on startup`,
);
if (shouldSetAsCurrent) {
console.log(`✅ Set version ${localVersion} as current version`);
}
if (shouldSetAsDefault) {
console.log(`✅ Set version ${localVersion} as default version`);
}
if (process.env.ENABLE_LOGGING === "true") {
logger.info(
`✅ Auto-imported new agent version ${localVersion} on startup (current: ${shouldSetAsCurrent}, default: ${shouldSetAsDefault})`,
);
}
} catch (error) {
console.error(
"❌ Failed to check/import agent version on startup:",
error.message,
);
if (process.env.ENABLE_LOGGING === "true") {
logger.error(
"Failed to check/import agent version on startup:",
error.message,
);
}
}
}
// Function to check and create default role permissions on startup
async function checkAndCreateRolePermissions() {
console.log("🔐 Starting role permissions auto-creation check...");
@@ -610,8 +416,6 @@ process.on("SIGTERM", async () => {
// Initialize dashboard preferences for all users
async function initializeDashboardPreferences() {
try {
console.log("🔧 Initializing dashboard preferences for all users...");
// Get all users
const users = await prisma.users.findMany({
select: {
@@ -628,12 +432,9 @@ async function initializeDashboardPreferences() {
});
if (users.length === 0) {
console.log(" No users found in database");
return;
}
console.log(`📊 Found ${users.length} users to initialize`);
let initializedCount = 0;
let updatedCount = 0;
@@ -648,10 +449,6 @@ async function initializeDashboardPreferences() {
if (!hasPreferences) {
// User has no preferences - create them
console.log(
`⚙️ Creating preferences for ${user.username} (${user.role})`,
);
const preferencesData = expectedPreferences.map((pref) => ({
id: require("uuid").v4(),
user_id: user.id,
@@ -667,18 +464,11 @@ async function initializeDashboardPreferences() {
});
initializedCount++;
console.log(
` ✅ Created ${expectedCardCount} cards based on permissions`,
);
} else {
// User already has preferences - check if they need updating
const currentCardCount = user.dashboard_preferences.length;
if (currentCardCount !== expectedCardCount) {
console.log(
`🔄 Updating preferences for ${user.username} (${user.role}) - ${currentCardCount}${expectedCardCount} cards`,
);
// Delete existing preferences
await prisma.dashboard_preferences.deleteMany({
where: { user_id: user.id },
@@ -700,29 +490,16 @@ async function initializeDashboardPreferences() {
});
updatedCount++;
console.log(
` ✅ Updated to ${expectedCardCount} cards based on permissions`,
);
} else {
console.log(
`${user.username} already has correct preferences (${currentCardCount} cards)`,
);
}
}
}
console.log(`\n📋 Dashboard Preferences Initialization Complete:`);
console.log(` - New users initialized: ${initializedCount}`);
console.log(` - Existing users updated: ${updatedCount}`);
console.log(
` - Users with correct preferences: ${users.length - initializedCount - updatedCount}`,
);
console.log(`\n🎯 Permission-based preferences:`);
console.log(` - Cards are now assigned based on actual user permissions`);
console.log(
` - Each card requires specific permissions (can_view_hosts, can_view_users, etc.)`,
);
console.log(` - Users only see cards they have permission to access`);
// Only show summary if there were changes
if (initializedCount > 0 || updatedCount > 0) {
console.log(
`✅ Dashboard preferences: ${initializedCount} initialized, ${updatedCount} updated`,
);
}
} catch (error) {
console.error("❌ Error initializing dashboard preferences:", error);
throw error;
@@ -889,9 +666,6 @@ async function startServer() {
throw initError; // Fail startup if settings can't be initialised
}
// Check and import agent version on startup
await checkAndImportAgentVersion();
// Check and create default role permissions on startup
await checkAndCreateRolePermissions();

View File

@@ -72,9 +72,11 @@ async function syncEnvironmentToSettings(currentSettings) {
if (currentValue !== convertedValue) {
updates[settingsField] = convertedValue;
hasChanges = true;
console.log(
`Environment variable ${envVar} (${envValue}) differs from settings ${settingsField} (${currentValue}), updating...`,
);
if (process.env.ENABLE_LOGGING === "true") {
console.log(
`Environment variable ${envVar} (${envValue}) differs from settings ${settingsField} (${currentValue}), updating...`,
);
}
}
}
}
@@ -89,7 +91,9 @@ async function syncEnvironmentToSettings(currentSettings) {
if (currentSettings.server_url !== constructedServerUrl) {
updates.server_url = constructedServerUrl;
hasChanges = true;
console.log(`Updating server_url to: ${constructedServerUrl}`);
if (process.env.ENABLE_LOGGING === "true") {
console.log(`Updating server_url to: ${constructedServerUrl}`);
}
}
// Update settings if there are changes
@@ -101,9 +105,11 @@ async function syncEnvironmentToSettings(currentSettings) {
updated_at: new Date(),
},
});
console.log(
`Synced ${Object.keys(updates).length} environment variables to settings`,
);
if (process.env.ENABLE_LOGGING === "true") {
console.log(
`Synced ${Object.keys(updates).length} environment variables to settings`,
);
}
return updatedSettings;
}

View File

@@ -109,7 +109,7 @@ class UpdateScheduler {
}
// Read version from package.json dynamically
let currentVersion = "1.2.6"; // fallback
let currentVersion = "1.2.7"; // fallback
try {
const packageJson = require("../../package.json");
if (packageJson?.version) {
@@ -219,7 +219,7 @@ class UpdateScheduler {
const httpsRepoUrl = `https://api.github.com/repos/${owner}/${repo}/releases/latest`;
// Get current version for User-Agent
let currentVersion = "1.2.6"; // fallback
let currentVersion = "1.2.7"; // fallback
try {
const packageJson = require("../../package.json");
if (packageJson?.version) {