Files
nixpanel-server/server/src/routes/nixserver/webserver.ts
T

107 lines
4.6 KiB
TypeScript
Raw Normal View History

2026-04-05 10:45:21 -05:00
import type { FastifyInstance } from 'fastify'
import { requireAdmin } from '../../middleware/auth.js'
import { exec } from '../../core/exec.js'
import { readFile } from 'fs/promises'
import { readdir } from 'fs/promises'
import path from 'path'
2026-04-05 10:45:21 -05:00
export default async function webserverRoutes(fastify: FastifyInstance) {
// GET /api/nixserver/webserver/status
2026-04-05 10:45:21 -05:00
fastify.get('/status', { preHandler: [requireAdmin] }, async (request, reply) => {
const [apache, fpm82, fpm83] = await Promise.all([
2026-04-05 10:45:21 -05:00
exec('systemctl', ['is-active', 'apache2']),
exec('systemctl', ['is-active', 'php8.2-fpm']),
exec('systemctl', ['is-active', 'php8.3-fpm']),
2026-04-05 10:45:21 -05:00
])
return reply.send({
success: true,
data: {
apache: { active: apache.stdout.trim() === 'active' },
php82fpm: { active: fpm82.stdout.trim() === 'active' },
php83fpm: { active: fpm83.stdout.trim() === 'active' },
2026-04-05 10:45:21 -05:00
},
})
})
// GET /api/nixserver/webserver/vhosts
2026-04-05 10:45:21 -05:00
fastify.get('/vhosts', { preHandler: [requireAdmin] }, async (request, reply) => {
try {
const files = await readdir('/etc/apache2/sites-enabled')
const vhosts = files
.filter(f => f.endsWith('.conf'))
.map(f => ({
path: `/etc/apache2/sites-enabled/${f}`,
domain: f.replace('.conf', ''),
}))
return reply.send({ success: true, data: vhosts })
} catch {
return reply.send({ success: true, data: [] })
}
2026-04-05 10:45:21 -05:00
})
// GET /api/nixserver/webserver/vhosts/:domain
2026-04-05 10:45:21 -05:00
fastify.get('/vhosts/:domain', { preHandler: [requireAdmin] }, async (request, reply) => {
const { domain } = request.params as { domain: string }
if (domain.includes('/') || domain.includes('..')) {
return reply.code(400).send({ success: false, error: 'Invalid domain' })
}
try {
const content = await readFile(`/etc/apache2/sites-available/${domain}.conf`, 'utf8')
2026-04-05 10:45:21 -05:00
return reply.send({ success: true, data: { domain, config: content } })
} catch {
return reply.code(404).send({ success: false, error: 'Vhost not found' })
}
})
// POST /api/nixserver/webserver/apache/test
fastify.post('/apache/test', { preHandler: [requireAdmin] }, async (request, reply) => {
const result = await exec('apache2ctl', ['configtest'])
2026-04-05 10:45:21 -05:00
return reply.send({
success: result.exitCode === 0,
data: { output: result.stdout + result.stderr },
})
})
// POST /api/nixserver/webserver/apache/reload
fastify.post('/apache/reload', { preHandler: [requireAdmin] }, async (request, reply) => {
const test = await exec('apache2ctl', ['configtest'])
2026-04-05 10:45:21 -05:00
if (test.exitCode !== 0) {
return reply.code(500).send({ success: false, error: `Config test failed: ${test.stderr}` })
}
const result = await exec('systemctl', ['reload', 'apache2'])
return reply.send({ success: result.exitCode === 0, data: { output: result.stdout } })
})
// POST /api/nixserver/webserver/fpm/reload
fastify.post('/fpm/reload', { preHandler: [requireAdmin] }, async (request, reply) => {
const { version } = (request.body ?? {}) as { version?: string }
const service = version === '8.2' ? 'php8.2-fpm' : 'php8.3-fpm'
const result = await exec('systemctl', ['reload', service])
2026-04-05 10:45:21 -05:00
return reply.send({ success: result.exitCode === 0, data: { output: result.stdout } })
})
// GET /api/nixserver/webserver/logs/:type — access or error log (last 200 lines)
2026-04-05 10:45:21 -05:00
fastify.get('/logs/:type', { preHandler: [requireAdmin] }, async (request, reply) => {
const { type } = request.params as { type: string }
const logFile = type === 'error'
? '/var/log/apache2/error.log'
: '/var/log/apache2/access.log'
const result = await exec('tail', ['-n', '200', logFile])
return reply.send({ success: true, data: { lines: result.stdout.split('\n').filter(Boolean) } })
})
// GET /api/nixserver/webserver/accounts/:username/logs/:type — per-account Apache logs
fastify.get('/accounts/:username/logs/:type', { preHandler: [requireAdmin] }, async (request, reply) => {
const { username, type } = request.params as { username: string; type: string }
// Validate username — only alphanum, dash, underscore allowed
if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
return reply.code(400).send({ success: false, error: 'Invalid username' })
}
const logFile = type === 'error'
? path.join('/home', username, 'logs', 'error.log')
: path.join('/home', username, 'logs', 'access.log')
2026-04-05 10:45:21 -05:00
const result = await exec('tail', ['-n', '200', logFile])
return reply.send({ success: true, data: { lines: result.stdout.split('\n').filter(Boolean) } })
})
}