feat(nixserver): version display and one-click panel upgrade

Add version badge to sidebar footer showing installed vs latest version,
fetched from /api/nixserver/system/version. Badge shows green when up to
date or amber with target version when an update is available.

Upgrade button triggers POST /api/nixserver/system/upgrade which spawns
scripts/update.sh detached (git pull + npm install + prisma migrate +
build + restart). Response is returned before the service restarts so
the client receives confirmation. Panel comes back online in ~60 s.
This commit is contained in:
NixReaper
2026-04-11 11:55:25 -05:00
parent da576bdbdc
commit a0e3fc7b1e
8 changed files with 189 additions and 7 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nixpanel/server",
"version": "0.4.2",
"version": "0.4.3",
"private": true,
"type": "module",
"main": "dist/index.js",
+1 -1
View File
@@ -73,7 +73,7 @@ export async function buildApp() {
fastify.get('/api/health', async () => ({
status: 'ok',
version: '0.4.2',
version: '0.4.3',
timestamp: new Date().toISOString(),
}))
+55
View File
@@ -1,10 +1,13 @@
import type { FastifyInstance } from 'fastify'
import { spawn } from 'child_process'
import prisma from '../../db/client.js'
import { requireAdmin } from '../../middleware/auth.js'
import { getSystemStats } from '../../core/system.js'
import { exec } from '../../core/exec.js'
import { readFile } from 'fs/promises'
const INSTALL_DIR = process.env.INSTALL_DIR ?? '/opt/nixpanel'
export default async function systemRoutes(fastify: FastifyInstance) {
// GET /api/nixserver/system/stats
fastify.get('/stats', { preHandler: [requireAdmin] }, async (request, reply) => {
@@ -121,4 +124,56 @@ export default async function systemRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { message: 'Settings updated' } })
})
// GET /api/nixserver/system/version
// Returns the installed version and checks GitHub for the latest available version.
fastify.get('/version', { preHandler: [requireAdmin] }, async (_request, reply) => {
// Read installed version from package.json
let currentVersion = '0.0.0'
try {
const pkg = JSON.parse(await readFile(`${INSTALL_DIR}/package.json`, 'utf8'))
currentVersion = pkg.version ?? '0.0.0'
} catch { /* running in dev or non-standard path */ }
// Check GitHub for latest version (5 s timeout, non-fatal)
let latestVersion: string | null = null
try {
const resp = await fetch(
'https://raw.githubusercontent.com/NixReaper/nixpanel-server/main/package.json',
{ signal: AbortSignal.timeout(5_000) }
)
if (resp.ok) {
const json = await resp.json() as { version?: string }
latestVersion = json.version ?? null
}
} catch { /* offline or rate-limited */ }
return reply.send({
success: true,
data: {
currentVersion,
latestVersion,
updateAvailable: latestVersion !== null && latestVersion !== currentVersion,
},
})
})
// POST /api/nixserver/system/upgrade
// Fires the update script in the background and returns immediately.
// The service will restart ~60 s later once the build finishes.
fastify.post('/upgrade', { preHandler: [requireAdmin] }, async (_request, reply) => {
reply.send({
success: true,
data: { message: 'Upgrade started. The panel will be back online in about 60 seconds.' },
})
// Delay slightly to ensure the HTTP response is flushed before the service restarts
setTimeout(() => {
const child = spawn('bash', [`${INSTALL_DIR}/scripts/update.sh`], {
detached: true,
stdio: 'ignore',
})
child.unref()
}, 800)
})
}