mirror of
https://github.com/9technologygroup/patchmon.net.git
synced 2025-10-23 16:13:57 +00:00
Merge changes from main: Add GetHomepage integration and update to v1.2.9
- Added gethomepageRoutes.js for GetHomepage integration - Updated all package.json files to version 1.2.9 - Updated agent script to version 1.2.9 - Updated version fallbacks in versionRoutes.js and updateScheduler.js - Updated setup.sh with version 1.2.9 - Merged GetHomepage integration UI (Integrations.jsx) - Updated docker-entrypoint.sh from main - Updated VersionUpdateTab component - Combined automation and gethomepage routes in server.js - Maintains both BullMQ automation and GetHomepage functionality
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# PatchMon Agent Script v1.2.8
|
# PatchMon Agent Script v1.2.9
|
||||||
# This script sends package update information to the PatchMon server using API credentials
|
# This script sends package update information to the PatchMon server using API credentials
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
PATCHMON_SERVER="${PATCHMON_SERVER:-http://localhost:3001}"
|
PATCHMON_SERVER="${PATCHMON_SERVER:-http://localhost:3001}"
|
||||||
API_VERSION="v1"
|
API_VERSION="v1"
|
||||||
AGENT_VERSION="1.2.8"
|
AGENT_VERSION="1.2.9"
|
||||||
CONFIG_FILE="/etc/patchmon/agent.conf"
|
CONFIG_FILE="/etc/patchmon/agent.conf"
|
||||||
CREDENTIALS_FILE="/etc/patchmon/credentials"
|
CREDENTIALS_FILE="/etc/patchmon/credentials"
|
||||||
LOG_FILE="/var/log/patchmon-agent.log"
|
LOG_FILE="/var/log/patchmon-agent.log"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "patchmon-backend",
|
"name": "patchmon-backend",
|
||||||
"version": "1.2.7",
|
"version": "1.2.9",
|
||||||
"description": "Backend API for Linux Patch Monitoring System",
|
"description": "Backend API for Linux Patch Monitoring System",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "src/server.js",
|
"main": "src/server.js",
|
||||||
|
236
backend/src/routes/gethomepageRoutes.js
Normal file
236
backend/src/routes/gethomepageRoutes.js
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const { createPrismaClient } = require("../config/database");
|
||||||
|
const bcrypt = require("bcryptjs");
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
const prisma = createPrismaClient();
|
||||||
|
|
||||||
|
// Middleware to authenticate API key
|
||||||
|
const authenticateApiKey = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const authHeader = req.headers.authorization;
|
||||||
|
|
||||||
|
if (!authHeader || !authHeader.startsWith("Basic ")) {
|
||||||
|
return res
|
||||||
|
.status(401)
|
||||||
|
.json({ error: "Missing or invalid authorization header" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode base64 credentials
|
||||||
|
const base64Credentials = authHeader.split(" ")[1];
|
||||||
|
const credentials = Buffer.from(base64Credentials, "base64").toString(
|
||||||
|
"ascii",
|
||||||
|
);
|
||||||
|
const [apiKey, apiSecret] = credentials.split(":");
|
||||||
|
|
||||||
|
if (!apiKey || !apiSecret) {
|
||||||
|
return res.status(401).json({ error: "Invalid credentials format" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the token in database
|
||||||
|
const token = await prisma.auto_enrollment_tokens.findUnique({
|
||||||
|
where: { token_key: apiKey },
|
||||||
|
include: {
|
||||||
|
users: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
role: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
console.log(`API key not found: ${apiKey}`);
|
||||||
|
return res.status(401).json({ error: "Invalid API key" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if token is active
|
||||||
|
if (!token.is_active) {
|
||||||
|
return res.status(401).json({ error: "API key is disabled" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if token has expired
|
||||||
|
if (token.expires_at && new Date(token.expires_at) < new Date()) {
|
||||||
|
return res.status(401).json({ error: "API key has expired" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if token is for gethomepage integration
|
||||||
|
if (token.metadata?.integration_type !== "gethomepage") {
|
||||||
|
return res.status(401).json({ error: "Invalid API key type" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the secret
|
||||||
|
const isValidSecret = await bcrypt.compare(apiSecret, token.token_secret);
|
||||||
|
if (!isValidSecret) {
|
||||||
|
return res.status(401).json({ error: "Invalid API secret" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check IP restrictions if any
|
||||||
|
if (token.allowed_ip_ranges && token.allowed_ip_ranges.length > 0) {
|
||||||
|
const clientIp = req.ip || req.connection.remoteAddress;
|
||||||
|
const forwardedFor = req.headers["x-forwarded-for"];
|
||||||
|
const realIp = req.headers["x-real-ip"];
|
||||||
|
|
||||||
|
// Get the actual client IP (considering proxies)
|
||||||
|
const actualClientIp = forwardedFor
|
||||||
|
? forwardedFor.split(",")[0].trim()
|
||||||
|
: realIp || clientIp;
|
||||||
|
|
||||||
|
const isAllowedIp = token.allowed_ip_ranges.some((range) => {
|
||||||
|
// Simple IP range check (can be enhanced for CIDR support)
|
||||||
|
return actualClientIp.startsWith(range) || actualClientIp === range;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isAllowedIp) {
|
||||||
|
console.log(
|
||||||
|
`IP validation failed. Client IP: ${actualClientIp}, Allowed ranges: ${token.allowed_ip_ranges.join(", ")}`,
|
||||||
|
);
|
||||||
|
return res.status(403).json({ error: "IP address not allowed" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update last used timestamp
|
||||||
|
await prisma.auto_enrollment_tokens.update({
|
||||||
|
where: { id: token.id },
|
||||||
|
data: { last_used_at: new Date() },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Attach token info to request
|
||||||
|
req.apiToken = token;
|
||||||
|
next();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("API key authentication error:", error);
|
||||||
|
res.status(500).json({ error: "Authentication failed" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get homepage widget statistics
|
||||||
|
router.get("/stats", authenticateApiKey, async (_req, res) => {
|
||||||
|
try {
|
||||||
|
// Get total hosts count
|
||||||
|
const totalHosts = await prisma.hosts.count({
|
||||||
|
where: { status: "active" },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get total outdated packages count
|
||||||
|
const totalOutdatedPackages = await prisma.host_packages.count({
|
||||||
|
where: { needs_update: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get total repositories count
|
||||||
|
const totalRepos = await prisma.repositories.count({
|
||||||
|
where: { is_active: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get hosts that need updates (have outdated packages)
|
||||||
|
const hostsNeedingUpdates = await prisma.hosts.count({
|
||||||
|
where: {
|
||||||
|
status: "active",
|
||||||
|
host_packages: {
|
||||||
|
some: {
|
||||||
|
needs_update: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get security updates count
|
||||||
|
const securityUpdates = await prisma.host_packages.count({
|
||||||
|
where: {
|
||||||
|
needs_update: true,
|
||||||
|
is_security_update: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get hosts with security updates
|
||||||
|
const hostsWithSecurityUpdates = await prisma.hosts.count({
|
||||||
|
where: {
|
||||||
|
status: "active",
|
||||||
|
host_packages: {
|
||||||
|
some: {
|
||||||
|
needs_update: true,
|
||||||
|
is_security_update: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get up-to-date hosts count
|
||||||
|
const upToDateHosts = totalHosts - hostsNeedingUpdates;
|
||||||
|
|
||||||
|
// Get recent update activity (last 24 hours)
|
||||||
|
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||||
|
const recentUpdates = await prisma.update_history.count({
|
||||||
|
where: {
|
||||||
|
timestamp: {
|
||||||
|
gte: oneDayAgo,
|
||||||
|
},
|
||||||
|
status: "success",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get OS distribution
|
||||||
|
const osDistribution = await prisma.hosts.groupBy({
|
||||||
|
by: ["os_type"],
|
||||||
|
where: { status: "active" },
|
||||||
|
_count: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
_count: {
|
||||||
|
id: "desc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Format OS distribution data
|
||||||
|
const osDistributionFormatted = osDistribution.map((os) => ({
|
||||||
|
name: os.os_type,
|
||||||
|
count: os._count.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Extract top 3 OS types for flat display in widgets
|
||||||
|
const top_os_1 = osDistributionFormatted[0] || { name: "None", count: 0 };
|
||||||
|
const top_os_2 = osDistributionFormatted[1] || { name: "None", count: 0 };
|
||||||
|
const top_os_3 = osDistributionFormatted[2] || { name: "None", count: 0 };
|
||||||
|
|
||||||
|
// Prepare response data
|
||||||
|
const stats = {
|
||||||
|
total_hosts: totalHosts,
|
||||||
|
total_outdated_packages: totalOutdatedPackages,
|
||||||
|
total_repos: totalRepos,
|
||||||
|
hosts_needing_updates: hostsNeedingUpdates,
|
||||||
|
up_to_date_hosts: upToDateHosts,
|
||||||
|
security_updates: securityUpdates,
|
||||||
|
hosts_with_security_updates: hostsWithSecurityUpdates,
|
||||||
|
recent_updates_24h: recentUpdates,
|
||||||
|
os_distribution: osDistributionFormatted,
|
||||||
|
// Flattened OS data for easy widget display
|
||||||
|
top_os_1_name: top_os_1.name,
|
||||||
|
top_os_1_count: top_os_1.count,
|
||||||
|
top_os_2_name: top_os_2.name,
|
||||||
|
top_os_2_count: top_os_2.count,
|
||||||
|
top_os_3_name: top_os_3.name,
|
||||||
|
top_os_3_count: top_os_3.count,
|
||||||
|
last_updated: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
res.json(stats);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching homepage stats:", error);
|
||||||
|
res.status(500).json({ error: "Failed to fetch statistics" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Health check endpoint for the API
|
||||||
|
router.get("/health", authenticateApiKey, async (req, res) => {
|
||||||
|
res.json({
|
||||||
|
status: "ok",
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
api_key: req.apiToken.token_name,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
@@ -14,13 +14,13 @@ const router = express.Router();
|
|||||||
function getCurrentVersion() {
|
function getCurrentVersion() {
|
||||||
try {
|
try {
|
||||||
const packageJson = require("../../package.json");
|
const packageJson = require("../../package.json");
|
||||||
return packageJson?.version || "1.2.7";
|
return packageJson?.version || "1.2.9";
|
||||||
} catch (packageError) {
|
} catch (packageError) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"Could not read version from package.json, using fallback:",
|
"Could not read version from package.json, using fallback:",
|
||||||
packageError.message,
|
packageError.message,
|
||||||
);
|
);
|
||||||
return "1.2.7";
|
return "1.2.9";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,11 +274,11 @@ router.get(
|
|||||||
) {
|
) {
|
||||||
console.log("GitHub API rate limited, providing fallback data");
|
console.log("GitHub API rate limited, providing fallback data");
|
||||||
latestRelease = {
|
latestRelease = {
|
||||||
tagName: "v1.2.7",
|
tagName: "v1.2.8",
|
||||||
version: "1.2.7",
|
version: "1.2.8",
|
||||||
publishedAt: "2025-10-02T17:12:53Z",
|
publishedAt: "2025-10-02T17:12:53Z",
|
||||||
htmlUrl:
|
htmlUrl:
|
||||||
"https://github.com/PatchMon/PatchMon/releases/tag/v1.2.7",
|
"https://github.com/PatchMon/PatchMon/releases/tag/v1.2.8",
|
||||||
};
|
};
|
||||||
latestCommit = {
|
latestCommit = {
|
||||||
sha: "cc89df161b8ea5d48ff95b0eb405fe69042052cd",
|
sha: "cc89df161b8ea5d48ff95b0eb405fe69042052cd",
|
||||||
|
@@ -61,9 +61,13 @@ const repositoryRoutes = require("./routes/repositoryRoutes");
|
|||||||
const versionRoutes = require("./routes/versionRoutes");
|
const versionRoutes = require("./routes/versionRoutes");
|
||||||
const tfaRoutes = require("./routes/tfaRoutes");
|
const tfaRoutes = require("./routes/tfaRoutes");
|
||||||
const searchRoutes = require("./routes/searchRoutes");
|
const searchRoutes = require("./routes/searchRoutes");
|
||||||
|
const autoEnrollmentRoutes = require("./routes/autoEnrollmentRoutes");
|
||||||
|
const gethomepageRoutes = require("./routes/gethomepageRoutes");
|
||||||
const automationRoutes = require("./routes/automationRoutes");
|
const automationRoutes = require("./routes/automationRoutes");
|
||||||
const { queueManager } = require("./services/automation");
|
const updateScheduler = require("./services/updateScheduler");
|
||||||
const { initSettings } = require("./services/settingsService");
|
const { initSettings } = require("./services/settingsService");
|
||||||
|
const { cleanup_expired_sessions } = require("./utils/session_manager");
|
||||||
|
const { queueManager } = require("./services/automation");
|
||||||
|
|
||||||
// Initialize Prisma client with optimized connection pooling for multiple instances
|
// Initialize Prisma client with optimized connection pooling for multiple instances
|
||||||
const prisma = createPrismaClient();
|
const prisma = createPrismaClient();
|
||||||
@@ -416,6 +420,12 @@ app.use(`/api/${apiVersion}/repositories`, repositoryRoutes);
|
|||||||
app.use(`/api/${apiVersion}/version`, versionRoutes);
|
app.use(`/api/${apiVersion}/version`, versionRoutes);
|
||||||
app.use(`/api/${apiVersion}/tfa`, tfaRoutes);
|
app.use(`/api/${apiVersion}/tfa`, tfaRoutes);
|
||||||
app.use(`/api/${apiVersion}/search`, searchRoutes);
|
app.use(`/api/${apiVersion}/search`, searchRoutes);
|
||||||
|
app.use(
|
||||||
|
`/api/${apiVersion}/auto-enrollment`,
|
||||||
|
authLimiter,
|
||||||
|
autoEnrollmentRoutes,
|
||||||
|
);
|
||||||
|
app.use(`/api/${apiVersion}/gethomepage`, gethomepageRoutes);
|
||||||
app.use(`/api/${apiVersion}/automation`, automationRoutes);
|
app.use(`/api/${apiVersion}/automation`, automationRoutes);
|
||||||
|
|
||||||
// Error handling middleware
|
// Error handling middleware
|
||||||
@@ -439,6 +449,10 @@ process.on("SIGINT", async () => {
|
|||||||
if (process.env.ENABLE_LOGGING === "true") {
|
if (process.env.ENABLE_LOGGING === "true") {
|
||||||
logger.info("SIGINT received, shutting down gracefully");
|
logger.info("SIGINT received, shutting down gracefully");
|
||||||
}
|
}
|
||||||
|
if (app.locals.session_cleanup_interval) {
|
||||||
|
clearInterval(app.locals.session_cleanup_interval);
|
||||||
|
}
|
||||||
|
updateScheduler.stop();
|
||||||
await queueManager.shutdown();
|
await queueManager.shutdown();
|
||||||
await disconnectPrisma(prisma);
|
await disconnectPrisma(prisma);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
@@ -448,6 +462,10 @@ process.on("SIGTERM", async () => {
|
|||||||
if (process.env.ENABLE_LOGGING === "true") {
|
if (process.env.ENABLE_LOGGING === "true") {
|
||||||
logger.info("SIGTERM received, shutting down gracefully");
|
logger.info("SIGTERM received, shutting down gracefully");
|
||||||
}
|
}
|
||||||
|
if (app.locals.session_cleanup_interval) {
|
||||||
|
clearInterval(app.locals.session_cleanup_interval);
|
||||||
|
}
|
||||||
|
updateScheduler.stop();
|
||||||
await queueManager.shutdown();
|
await queueManager.shutdown();
|
||||||
await disconnectPrisma(prisma);
|
await disconnectPrisma(prisma);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
@@ -723,13 +741,34 @@ async function startServer() {
|
|||||||
// Schedule recurring jobs
|
// Schedule recurring jobs
|
||||||
await queueManager.scheduleAllJobs();
|
await queueManager.scheduleAllJobs();
|
||||||
|
|
||||||
|
// Initial session cleanup
|
||||||
|
await cleanup_expired_sessions();
|
||||||
|
|
||||||
|
// Schedule session cleanup every hour
|
||||||
|
const session_cleanup_interval = setInterval(
|
||||||
|
async () => {
|
||||||
|
try {
|
||||||
|
await cleanup_expired_sessions();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Session cleanup error:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
60 * 60 * 1000,
|
||||||
|
); // Every hour
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
if (process.env.ENABLE_LOGGING === "true") {
|
if (process.env.ENABLE_LOGGING === "true") {
|
||||||
logger.info(`Server running on port ${PORT}`);
|
logger.info(`Server running on port ${PORT}`);
|
||||||
logger.info(`Environment: ${process.env.NODE_ENV}`);
|
logger.info(`Environment: ${process.env.NODE_ENV}`);
|
||||||
logger.info("✅ BullMQ queue manager started");
|
logger.info("✅ Session cleanup scheduled (every hour)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start update scheduler
|
||||||
|
updateScheduler.start();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Store interval for cleanup on shutdown
|
||||||
|
app.locals.session_cleanup_interval = session_cleanup_interval;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ Failed to start server:", error.message);
|
console.error("❌ Failed to start server:", error.message);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
@@ -104,7 +104,7 @@ class UpdateScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read version from package.json dynamically
|
// Read version from package.json dynamically
|
||||||
let currentVersion = "1.2.7"; // fallback
|
let currentVersion = "1.2.9"; // fallback
|
||||||
try {
|
try {
|
||||||
const packageJson = require("../../package.json");
|
const packageJson = require("../../package.json");
|
||||||
if (packageJson?.version) {
|
if (packageJson?.version) {
|
||||||
@@ -214,7 +214,7 @@ class UpdateScheduler {
|
|||||||
const httpsRepoUrl = `https://api.github.com/repos/${owner}/${repo}/releases/latest`;
|
const httpsRepoUrl = `https://api.github.com/repos/${owner}/${repo}/releases/latest`;
|
||||||
|
|
||||||
// Get current version for User-Agent
|
// Get current version for User-Agent
|
||||||
let currentVersion = "1.2.7"; // fallback
|
let currentVersion = "1.2.9"; // fallback
|
||||||
try {
|
try {
|
||||||
const packageJson = require("../../package.json");
|
const packageJson = require("../../package.json");
|
||||||
if (packageJson?.version) {
|
if (packageJson?.version) {
|
||||||
|
@@ -8,19 +8,94 @@ log() {
|
|||||||
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" >&2
|
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
# Copy files from agents_backup to agents if agents directory is empty and no .sh files are present
|
# Function to extract version from agent script
|
||||||
if [ -d "/app/agents" ] && [ -z "$(find /app/agents -maxdepth 1 -type f -name '*.sh' | head -n 1)" ]; then
|
get_agent_version() {
|
||||||
if [ -d "/app/agents_backup" ]; then
|
local file="$1"
|
||||||
log "Agents directory is empty, copying from backup..."
|
if [ -f "$file" ]; then
|
||||||
cp -r /app/agents_backup/* /app/agents/
|
grep -m 1 '^AGENT_VERSION=' "$file" | cut -d'"' -f2 2>/dev/null || echo "0.0.0"
|
||||||
else
|
else
|
||||||
log "Warning: agents_backup directory not found"
|
echo "0.0.0"
|
||||||
fi
|
fi
|
||||||
else
|
}
|
||||||
log "Agents directory already contains files, skipping copy"
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "Starting PatchMon Backend (${NODE_ENV:-production})..."
|
# Function to compare versions (returns 0 if $1 > $2)
|
||||||
|
version_greater() {
|
||||||
|
# Use sort -V for version comparison
|
||||||
|
test "$(printf '%s\n' "$1" "$2" | sort -V | tail -n1)" = "$1" && test "$1" != "$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check and update agent files if necessary
|
||||||
|
update_agents() {
|
||||||
|
local backup_agent="/app/agents_backup/patchmon-agent.sh"
|
||||||
|
local current_agent="/app/agents/patchmon-agent.sh"
|
||||||
|
|
||||||
|
# Check if agents directory exists
|
||||||
|
if [ ! -d "/app/agents" ]; then
|
||||||
|
log "ERROR: /app/agents directory not found"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if backup exists
|
||||||
|
if [ ! -d "/app/agents_backup" ]; then
|
||||||
|
log "WARNING: agents_backup directory not found, skipping agent update"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get versions
|
||||||
|
local backup_version=$(get_agent_version "$backup_agent")
|
||||||
|
local current_version=$(get_agent_version "$current_agent")
|
||||||
|
|
||||||
|
log "Agent version check:"
|
||||||
|
log " Image version: ${backup_version}"
|
||||||
|
log " Volume version: ${current_version}"
|
||||||
|
|
||||||
|
# Determine if update is needed
|
||||||
|
local needs_update=0
|
||||||
|
|
||||||
|
# Case 1: No agents in volume (first time setup)
|
||||||
|
if [ -z "$(find /app/agents -maxdepth 1 -type f -name '*.sh' 2>/dev/null | head -n 1)" ]; then
|
||||||
|
log "Agents directory is empty - performing initial copy"
|
||||||
|
needs_update=1
|
||||||
|
# Case 2: Backup version is newer
|
||||||
|
elif version_greater "$backup_version" "$current_version"; then
|
||||||
|
log "Newer agent version available (${backup_version} > ${current_version})"
|
||||||
|
needs_update=1
|
||||||
|
else
|
||||||
|
log "Agents are up to date"
|
||||||
|
needs_update=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Perform update if needed
|
||||||
|
if [ $needs_update -eq 1 ]; then
|
||||||
|
log "Updating agents to version ${backup_version}..."
|
||||||
|
|
||||||
|
# Create backup of existing agents if they exist
|
||||||
|
if [ -f "$current_agent" ]; then
|
||||||
|
local backup_timestamp=$(date +%Y%m%d_%H%M%S)
|
||||||
|
local backup_name="/app/agents/patchmon-agent.sh.backup.${backup_timestamp}"
|
||||||
|
cp "$current_agent" "$backup_name" 2>/dev/null || true
|
||||||
|
log "Previous agent backed up to: $(basename $backup_name)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy new agents
|
||||||
|
cp -r /app/agents_backup/* /app/agents/
|
||||||
|
|
||||||
|
# Verify update
|
||||||
|
local new_version=$(get_agent_version "$current_agent")
|
||||||
|
if [ "$new_version" = "$backup_version" ]; then
|
||||||
|
log "✅ Agents successfully updated to version ${new_version}"
|
||||||
|
else
|
||||||
|
log "⚠️ Warning: Agent update may have failed (expected: ${backup_version}, got: ${new_version})"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
log "PatchMon Backend Container Starting..."
|
||||||
|
log "Environment: ${NODE_ENV:-production}"
|
||||||
|
|
||||||
|
# Update agents (version-aware)
|
||||||
|
update_agents
|
||||||
|
|
||||||
log "Running database migrations..."
|
log "Running database migrations..."
|
||||||
npx prisma migrate deploy
|
npx prisma migrate deploy
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "patchmon-frontend",
|
"name": "patchmon-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.2.7",
|
"version": "1.2.9",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@@ -128,12 +128,14 @@ const VersionUpdateTab = () => {
|
|||||||
<span className="text-lg font-mono text-secondary-900 dark:text-white">
|
<span className="text-lg font-mono text-secondary-900 dark:text-white">
|
||||||
{versionInfo.github.latestRelease.tagName}
|
{versionInfo.github.latestRelease.tagName}
|
||||||
</span>
|
</span>
|
||||||
<div className="text-xs text-secondary-500 dark:text-secondary-400">
|
{versionInfo.github.latestRelease.publishedAt && (
|
||||||
Published:{" "}
|
<div className="text-xs text-secondary-500 dark:text-secondary-400">
|
||||||
{new Date(
|
Published:{" "}
|
||||||
versionInfo.github.latestRelease.publishedAt,
|
{new Date(
|
||||||
).toLocaleDateString()}
|
versionInfo.github.latestRelease.publishedAt,
|
||||||
</div>
|
).toLocaleDateString()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
File diff suppressed because it is too large
Load Diff
8
package-lock.json
generated
8
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "patchmon",
|
"name": "patchmon",
|
||||||
"version": "1.2.7",
|
"version": "1.2.9",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "patchmon",
|
"name": "patchmon",
|
||||||
"version": "1.2.7",
|
"version": "1.2.9",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"backend",
|
"backend",
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
},
|
},
|
||||||
"backend": {
|
"backend": {
|
||||||
"name": "patchmon-backend",
|
"name": "patchmon-backend",
|
||||||
"version": "1.2.7",
|
"version": "1.2.9",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bull-board/api": "^6.13.0",
|
"@bull-board/api": "^6.13.0",
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
},
|
},
|
||||||
"frontend": {
|
"frontend": {
|
||||||
"name": "patchmon-frontend",
|
"name": "patchmon-frontend",
|
||||||
"version": "1.2.7",
|
"version": "1.2.9",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "patchmon",
|
"name": "patchmon",
|
||||||
"version": "1.2.7",
|
"version": "1.2.9",
|
||||||
"description": "Linux Patch Monitoring System",
|
"description": "Linux Patch Monitoring System",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
6
setup.sh
6
setup.sh
@@ -34,7 +34,7 @@ BLUE='\033[0;34m'
|
|||||||
NC='\033[0m' # No Color
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
# Global variables
|
# Global variables
|
||||||
SCRIPT_VERSION="self-hosting-install.sh v1.2.7-selfhost-2025-01-20-1"
|
SCRIPT_VERSION="self-hosting-install.sh v1.2.9-selfhost-2025-10-11-1"
|
||||||
DEFAULT_GITHUB_REPO="https://github.com/PatchMon/PatchMon.git"
|
DEFAULT_GITHUB_REPO="https://github.com/PatchMon/PatchMon.git"
|
||||||
FQDN=""
|
FQDN=""
|
||||||
CUSTOM_FQDN=""
|
CUSTOM_FQDN=""
|
||||||
@@ -834,7 +834,7 @@ EOF
|
|||||||
cat > frontend/.env << EOF
|
cat > frontend/.env << EOF
|
||||||
VITE_API_URL=$SERVER_PROTOCOL_SEL://$FQDN/api/v1
|
VITE_API_URL=$SERVER_PROTOCOL_SEL://$FQDN/api/v1
|
||||||
VITE_APP_NAME=PatchMon
|
VITE_APP_NAME=PatchMon
|
||||||
VITE_APP_VERSION=1.2.7
|
VITE_APP_VERSION=1.2.9
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
print_status "Environment files created"
|
print_status "Environment files created"
|
||||||
@@ -1206,7 +1206,7 @@ create_agent_version() {
|
|||||||
|
|
||||||
# Priority 2: Use fallback version if not found
|
# Priority 2: Use fallback version if not found
|
||||||
if [ "$current_version" = "N/A" ] || [ -z "$current_version" ]; then
|
if [ "$current_version" = "N/A" ] || [ -z "$current_version" ]; then
|
||||||
current_version="1.2.7"
|
current_version="1.2.9"
|
||||||
print_warning "Could not determine version, using fallback: $current_version"
|
print_warning "Could not determine version, using fallback: $current_version"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user