feat: v0.4.0 — Apache service stack, shell access, log viewer, changelog
Service stack (backend): - webserver.ts rewritten: apache2/php-fpm routes replacing nginx; per-account Apache log endpoint; PHP-FPM reload per version; apache2ctl configtest/reload - accounts.ts: added PUT /:id/shell to toggle /bin/bash vs /usr/sbin/nologin, runs usermod -s and writes audit log New UI features (nixserver): - ShellAccess page: grant/revoke SSH access per account via AccountPicker - ApacheLogs page: live log viewer (access/error) per account; replaces stub - NginxLogs: re-exports ApacheLogs (NixPanel uses Apache, not NGINX) - WebServer page: shows apache2, php8.2-fpm, php8.3-fpm status with reload buttons; config test; Apache log viewer Bug fixes: - Accounts.tsx: Lucide icon size prop type widened (string|number); removed unused Users import; dropped duplicate NGINX log card - Removed soon badge from ShellAccess and ApacheLogs entries Project: - CHANGELOG.md created with full history from 0.2.x through 0.4.0 - All packages bumped from 0.3.2 to 0.4.0
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import { config } from '../config.js'
|
||||
|
||||
let licenseValid = false
|
||||
let lastChecked: Date | null = null
|
||||
|
||||
/**
|
||||
* Validate the license with the licensing server on startup.
|
||||
* Throws if the license is invalid or the server is unreachable.
|
||||
*/
|
||||
export async function validateOnStartup(): Promise<void> {
|
||||
if (!config.licensing.licenseKey) {
|
||||
if (config.env === 'development') {
|
||||
licenseValid = true
|
||||
return
|
||||
}
|
||||
throw new Error('LICENSE_KEY is not set. Set it in .env to activate NixPanel.')
|
||||
}
|
||||
|
||||
await checkLicense()
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a heartbeat to the licensing server every 6 hours.
|
||||
* If the license becomes invalid, logs a critical event but does NOT shut down —
|
||||
* the panel keeps running to avoid accidental outages.
|
||||
*/
|
||||
export async function heartbeat(): Promise<void> {
|
||||
if (!config.licensing.licenseKey) return
|
||||
|
||||
try {
|
||||
await checkLicense()
|
||||
} catch (err) {
|
||||
console.error('[license] Heartbeat failed:', err)
|
||||
// Log to DB if available — non-fatal, don't crash
|
||||
try {
|
||||
const { default: prisma } = await import('../db/client.js')
|
||||
await prisma.securityEvent.create({
|
||||
data: {
|
||||
type: 'license_heartbeat_failed',
|
||||
severity: 'warning',
|
||||
message: `License heartbeat failed: ${String(err)}`,
|
||||
},
|
||||
})
|
||||
} catch {
|
||||
// DB may be unavailable — ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function isLicenseValid(): boolean {
|
||||
return licenseValid
|
||||
}
|
||||
|
||||
export function getLastChecked(): Date | null {
|
||||
return lastChecked
|
||||
}
|
||||
|
||||
async function checkLicense(): Promise<void> {
|
||||
const url = `${config.licensing.serverUrl}/api/v1/validate`
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
licenseKey: config.licensing.licenseKey,
|
||||
serverIp: await getServerIp(),
|
||||
}),
|
||||
signal: AbortSignal.timeout(15_000),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
licenseValid = false
|
||||
throw new Error(`License server returned ${response.status}`)
|
||||
}
|
||||
|
||||
const data = await response.json() as { valid: boolean; message?: string }
|
||||
licenseValid = data.valid
|
||||
lastChecked = new Date()
|
||||
|
||||
if (!data.valid) {
|
||||
throw new Error(data.message ?? 'License is not valid')
|
||||
}
|
||||
}
|
||||
|
||||
async function getServerIp(): Promise<string> {
|
||||
try {
|
||||
const { createConnection } = await import('net')
|
||||
return new Promise((resolve) => {
|
||||
const socket = createConnection(80, '8.8.8.8')
|
||||
socket.on('connect', () => {
|
||||
resolve(socket.localAddress ?? '')
|
||||
socket.destroy()
|
||||
})
|
||||
socket.on('error', () => resolve(''))
|
||||
})
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user