Relaid out settings page and configured agent and other communication to use curl flags which can be configured to ignore ssl cert if self-hosted.

This commit is contained in:
Muhammad Ibrahim
2025-09-30 21:38:13 +01:00
parent ed0cf79b53
commit 8be25283dc
19 changed files with 214 additions and 63 deletions

View File

@@ -0,0 +1,10 @@
-- Fix dashboard preferences unique constraint
-- This migration fixes the unique constraint on dashboard_preferences table
-- Drop existing indexes if they exist
DROP INDEX IF EXISTS "dashboard_preferences_card_id_key";
DROP INDEX IF EXISTS "dashboard_preferences_user_id_card_id_key";
DROP INDEX IF EXISTS "dashboard_preferences_user_id_key";
-- Add the correct unique constraint
ALTER TABLE "dashboard_preferences" ADD CONSTRAINT "dashboard_preferences_user_id_card_id_key" UNIQUE ("user_id", "card_id");

View File

@@ -0,0 +1,4 @@
-- Add ignore_ssl_self_signed column to settings table
-- This allows users to configure whether curl commands should ignore SSL certificate validation
ALTER TABLE "settings" ADD COLUMN "ignore_ssl_self_signed" BOOLEAN NOT NULL DEFAULT false;

View File

@@ -7,7 +7,6 @@ datasource db {
url = env("DATABASE_URL")
}
model dashboard_preferences {
id String @id
user_id String
@@ -157,6 +156,7 @@ model settings {
update_available Boolean @default(false)
signup_enabled Boolean @default(false)
default_user_role String @default("user")
ignore_ssl_self_signed Boolean @default(false)
}
model update_history {
@@ -175,8 +175,6 @@ model users {
username String @unique
email String @unique
password_hash String
first_name String?
last_name String?
role String @default("admin")
is_active Boolean @default(true)
last_login DateTime?
@@ -185,5 +183,7 @@ model users {
tfa_backup_codes String?
tfa_enabled Boolean @default(false)
tfa_secret String?
first_name String?
last_name String?
dashboard_preferences dashboard_preferences[]
}

View File

@@ -45,11 +45,26 @@ router.get("/agent/download", async (req, res) => {
}
// Read file and convert line endings
const scriptContent = fs
let scriptContent = fs
.readFileSync(agentPath, "utf8")
.replace(/\r\n/g, "\n")
.replace(/\r/g, "\n");
// Determine curl flags dynamically from settings for consistency
let curlFlags = "-s";
try {
const settings = await prisma.settings.findFirst();
if (settings && settings.ignore_ssl_self_signed === true) {
curlFlags = "-sk";
}
} catch (_) {}
// Inject the curl flags into the script
scriptContent = scriptContent.replace(
'CURL_FLAGS=""',
`CURL_FLAGS="${curlFlags}"`,
);
res.setHeader("Content-Type", "application/x-shellscript");
res.setHeader(
"Content-Disposition",
@@ -1101,11 +1116,21 @@ router.get("/install", async (req, res) => {
);
}
// Inject the API credentials and server URL into the script as environment variables
// Determine curl flags dynamically from settings (ignore self-signed)
let curlFlags = "-s";
try {
const settings = await prisma.settings.findFirst();
if (settings && settings.ignore_ssl_self_signed === true) {
curlFlags = "-sk";
}
} catch (_) {}
// Inject the API credentials, server URL, and curl flags into the script
const envVars = `#!/bin/bash
export PATCHMON_URL="${serverUrl}"
export API_ID="${host.api_id}"
export API_KEY="${host.api_key}"
export CURL_FLAGS="${curlFlags}"
`;
@@ -1141,7 +1166,24 @@ router.get("/remove", async (_req, res) => {
}
// Read the script content
const script = fs.readFileSync(scriptPath, "utf8");
let script = fs.readFileSync(scriptPath, "utf8");
// Convert line endings
script = script.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
// Determine curl flags dynamically from settings for consistency
let curlFlags = "-s";
try {
const settings = await prisma.settings.findFirst();
if (settings && settings.ignore_ssl_self_signed === true) {
curlFlags = "-sk";
}
} catch (_) {}
// Prepend environment for CURL_FLAGS so script can use it if needed
const envPrefix = `#!/bin/bash\nexport CURL_FLAGS="${curlFlags}"\n\n`;
script = script.replace(/^#!/, "#");
script = envPrefix + script;
// Set appropriate headers for script download
res.setHeader("Content-Type", "text/plain");

View File

@@ -165,19 +165,31 @@ router.put(
requireManageSettings,
[
body("serverProtocol")
.optional()
.isIn(["http", "https"])
.withMessage("Protocol must be http or https"),
body("serverHost")
.optional()
.isLength({ min: 1 })
.withMessage("Server host is required"),
body("serverPort")
.optional()
.isInt({ min: 1, max: 65535 })
.withMessage("Port must be between 1 and 65535"),
body("updateInterval")
.optional()
.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("autoUpdate")
.optional()
.isBoolean()
.withMessage("Auto update must be a boolean"),
body("ignoreSslSelfSigned")
.optional()
.isBoolean()
.withMessage("Ignore SSL self-signed must be a boolean"),
body("signupEnabled")
.optional()
.isBoolean()
.withMessage("Signup enabled must be a boolean"),
body("defaultUserRole")
@@ -218,6 +230,7 @@ router.put(
serverPort,
updateInterval,
autoUpdate,
ignoreSslSelfSigned,
signupEnabled,
defaultUserRole,
githubRepoUrl,
@@ -229,32 +242,43 @@ router.put(
const currentSettings = await getSettings();
const oldUpdateInterval = currentSettings.update_interval;
// Update settings using the service
const normalizedInterval = normalizeUpdateInterval(updateInterval || 60);
// Build update object with only provided fields
const updateData = {};
const updatedSettings = await updateSettings(currentSettings.id, {
server_protocol: serverProtocol,
server_host: serverHost,
server_port: serverPort,
update_interval: normalizedInterval,
auto_update: autoUpdate || false,
signup_enabled: signupEnabled || false,
default_user_role:
defaultUserRole || process.env.DEFAULT_USER_ROLE || "user",
github_repo_url:
githubRepoUrl !== undefined
? githubRepoUrl
: "git@github.com:9technologygroup/patchmon.net.git",
repository_type: repositoryType || "public",
ssh_key_path: sshKeyPath || null,
});
if (serverProtocol !== undefined)
updateData.server_protocol = serverProtocol;
if (serverHost !== undefined) updateData.server_host = serverHost;
if (serverPort !== undefined) updateData.server_port = serverPort;
if (updateInterval !== undefined) {
updateData.update_interval = normalizeUpdateInterval(updateInterval);
}
if (autoUpdate !== undefined) updateData.auto_update = autoUpdate;
if (ignoreSslSelfSigned !== undefined)
updateData.ignore_ssl_self_signed = ignoreSslSelfSigned;
if (signupEnabled !== undefined)
updateData.signup_enabled = signupEnabled;
if (defaultUserRole !== undefined)
updateData.default_user_role = defaultUserRole;
if (githubRepoUrl !== undefined)
updateData.github_repo_url = githubRepoUrl;
if (repositoryType !== undefined)
updateData.repository_type = repositoryType;
if (sshKeyPath !== undefined) updateData.ssh_key_path = sshKeyPath;
const updatedSettings = await updateSettings(
currentSettings.id,
updateData,
);
console.log("Settings updated successfully:", updatedSettings);
// If update interval changed, trigger crontab updates on all hosts with auto-update enabled
if (oldUpdateInterval !== normalizedInterval) {
if (
updateInterval !== undefined &&
oldUpdateInterval !== updateData.update_interval
) {
console.log(
`Update interval changed from ${oldUpdateInterval} to ${normalizedInterval} minutes. Triggering crontab updates...`,
`Update interval changed from ${oldUpdateInterval} to ${updateData.update_interval} minutes. Triggering crontab updates...`,
);
await triggerCrontabUpdates();
}

View File

@@ -43,6 +43,7 @@ async function createSettingsFromEnvironment() {
update_interval: 60,
auto_update: false,
signup_enabled: false,
ignore_ssl_self_signed: false,
updated_at: new Date(),
},
});