feat: rename WHM→NixServer, cPanel→NixClient, bump v0.2.0

- Rename frontend workspaces: whm/ → nixserver/, cpanel/ → nixclient/
- Rename server route dirs: routes/whm/ → routes/nixserver/, routes/cpanel/ → routes/nixclient/
- Update all API prefixes: /api/whm → /api/nixserver, /api/cpanel → /api/nixclient
- Rename env vars: WHM_PORT → NIXSERVER_PORT, CPANEL_PORT → NIXCLIENT_PORT
- Update config properties: whmPort → nixserverPort, cpanelPort → nixclientPort
- Update all UI labels, login pages, sidebar branding, and error messages
- Update installer and update scripts to match new names
- Add cpanel email route (missing from initial scaffold)
- Add .gitattributes to enforce LF line endings
- Bump version to 0.2.0 across all package.json files
This commit is contained in:
NixReaper
2026-04-05 10:54:15 -05:00
parent 16245b2d4b
commit 92d08d7b79
84 changed files with 243 additions and 234 deletions
+2 -2
View File
@@ -9,8 +9,8 @@ JWT_REFRESH_EXPIRES_IN="7d"
# Server # Server
NODE_ENV="production" NODE_ENV="production"
WHM_PORT=2087 NIXSERVER_PORT=2087
CPANEL_PORT=2083 NIXCLIENT_PORT=2083
HOST="0.0.0.0" HOST="0.0.0.0"
# Admin # Admin
+9
View File
@@ -0,0 +1,9 @@
# Default — normalize to LF on commit
* text=auto eol=lf
# Shell scripts must always be LF
*.sh text eol=lf
# Windows-only files (if any)
*.bat text eol=crlf
*.cmd text eol=crlf
@@ -1,6 +1,6 @@
{ {
"name": "@nixpanel/cpanel", "name": "@nixpanel/cpanel",
"version": "0.1.0", "version": "0.2.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -34,7 +34,7 @@ export default function Layout() {
</div> </div>
{sidebarOpen && ( {sidebarOpen && (
<div className="overflow-hidden"> <div className="overflow-hidden">
<p className="text-white text-sm font-semibold leading-tight">NixPanel</p> <p className="text-white text-sm font-semibold leading-tight">NixClient</p>
<p className="text-[#64748b] text-xs truncate">{user?.domain}</p> <p className="text-[#64748b] text-xs truncate">{user?.domain}</p>
</div> </div>
)} )}
@@ -40,7 +40,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const login = async (username: string, password: string) => { const login = async (username: string, password: string) => {
const { data } = await api.post('/auth/login', { username, password }) const { data } = await api.post('/auth/login', { username, password })
if (data.data.role !== 'user') throw new Error('Use WHM to log in as admin or reseller') if (data.data.role !== 'user') throw new Error('Use NixServer to log in as admin or reseller')
localStorage.setItem('access_token', data.data.accessToken) localStorage.setItem('access_token', data.data.accessToken)
localStorage.setItem('refresh_token', data.data.refreshToken) localStorage.setItem('refresh_token', data.data.refreshToken)
setUser(data.data.user) setUser(data.data.user)
@@ -22,7 +22,7 @@ export default function CronJobs() {
const fetch = async () => { const fetch = async () => {
setLoading(true) setLoading(true)
try { const r = await api.get('/cpanel/cron'); setJobs(r.data.data) } try { const r = await api.get('/nixclient/cron'); setJobs(r.data.data) }
finally { setLoading(false) } finally { setLoading(false) }
} }
@@ -30,18 +30,18 @@ export default function CronJobs() {
const save = async (e: React.FormEvent) => { const save = async (e: React.FormEvent) => {
e.preventDefault(); setSaving(true); setError('') e.preventDefault(); setSaving(true); setError('')
try { await api.post('/cpanel/cron', form); setShowModal(false); setForm({ schedule: '0 * * * *', command: '', comment: '', enabled: true }); fetch() } try { await api.post('/nixclient/cron', form); setShowModal(false); setForm({ schedule: '0 * * * *', command: '', comment: '', enabled: true }); fetch() }
catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') }
finally { setSaving(false) } finally { setSaving(false) }
} }
const del = async (id: number) => { const del = async (id: number) => {
if (!confirm('Delete this cron job?')) return if (!confirm('Delete this cron job?')) return
try { await api.delete(`/cpanel/cron/${id}`); fetch() } catch {} try { await api.delete(`/nixclient/cron/${id}`); fetch() } catch {}
} }
const toggle = async (job: CronJob) => { const toggle = async (job: CronJob) => {
try { await api.put(`/cpanel/cron/${job.id}`, { enabled: !job.enabled }); fetch() } catch {} try { await api.put(`/nixclient/cron/${job.id}`, { enabled: !job.enabled }); fetch() } catch {}
} }
return ( return (
@@ -18,7 +18,7 @@ export default function Databases() {
const fetch = async () => { const fetch = async () => {
setLoading(true) setLoading(true)
try { const r = await api.get('/cpanel/databases'); setDbs(r.data.data) } try { const r = await api.get('/nixclient/databases'); setDbs(r.data.data) }
finally { setLoading(false) } finally { setLoading(false) }
} }
@@ -26,27 +26,27 @@ export default function Databases() {
const createDb = async (e: React.FormEvent) => { const createDb = async (e: React.FormEvent) => {
e.preventDefault(); setSaving(true); setError('') e.preventDefault(); setSaving(true); setError('')
try { await api.post('/cpanel/databases', dbForm); setShowDbModal(false); setDbForm({ name: '' }); fetch() } try { await api.post('/nixclient/databases', dbForm); setShowDbModal(false); setDbForm({ name: '' }); fetch() }
catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') }
finally { setSaving(false) } finally { setSaving(false) }
} }
const deleteDb = async (id: number) => { const deleteDb = async (id: number) => {
if (!confirm('Delete this database and all its data?')) return if (!confirm('Delete this database and all its data?')) return
try { await api.delete(`/cpanel/databases/${id}`); fetch() } try { await api.delete(`/nixclient/databases/${id}`); fetch() }
catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') }
} }
const createUser = async (e: React.FormEvent) => { const createUser = async (e: React.FormEvent) => {
e.preventDefault(); if (!showUserModal) return e.preventDefault(); if (!showUserModal) return
setSaving(true); setError('') setSaving(true); setError('')
try { await api.post(`/cpanel/databases/${showUserModal}/users`, userForm); setShowUserModal(null); setUserForm({ username: '', password: '', privileges: 'ALL' }); fetch() } try { await api.post(`/nixclient/databases/${showUserModal}/users`, userForm); setShowUserModal(null); setUserForm({ username: '', password: '', privileges: 'ALL' }); fetch() }
catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') }
finally { setSaving(false) } finally { setSaving(false) }
} }
const deleteUser = async (dbId: number, userId: number) => { const deleteUser = async (dbId: number, userId: number) => {
try { await api.delete(`/cpanel/databases/${dbId}/users/${userId}`); fetch() } try { await api.delete(`/nixclient/databases/${dbId}/users/${userId}`); fetch() }
catch {} catch {}
} }
@@ -18,7 +18,7 @@ export default function Domains() {
const fetch = async () => { const fetch = async () => {
setLoading(true) setLoading(true)
try { try {
const r = await api.get(`/cpanel/domains/${tab}`) const r = await api.get(`/nixclient/domains/${tab}`)
setItems(r.data.data) setItems(r.data.data)
} finally { setLoading(false) } } finally { setLoading(false) }
} }
@@ -28,9 +28,9 @@ export default function Domains() {
const save = async (e: React.FormEvent) => { const save = async (e: React.FormEvent) => {
e.preventDefault(); setSaving(true); setError('') e.preventDefault(); setSaving(true); setError('')
try { try {
if (tab === 'subdomains') await api.post('/cpanel/domains/subdomains', { subdomain: form.subdomain }) if (tab === 'subdomains') await api.post('/nixclient/domains/subdomains', { subdomain: form.subdomain })
else if (tab === 'addon') await api.post('/cpanel/domains/addon', { domain: form.domain }) else if (tab === 'addon') await api.post('/nixclient/domains/addon', { domain: form.domain })
else await api.post('/cpanel/domains/parked', { domain: form.domain }) else await api.post('/nixclient/domains/parked', { domain: form.domain })
setShowModal(false); setForm({ subdomain: '', domain: '' }); fetch() setShowModal(false); setForm({ subdomain: '', domain: '' }); fetch()
} catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } } catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') }
finally { setSaving(false) } finally { setSaving(false) }
@@ -39,9 +39,9 @@ export default function Domains() {
const del = async (id: number) => { const del = async (id: number) => {
if (!confirm('Remove this domain?')) return if (!confirm('Remove this domain?')) return
try { try {
if (tab === 'subdomains') await api.delete(`/cpanel/domains/subdomains/${id}`) if (tab === 'subdomains') await api.delete(`/nixclient/domains/subdomains/${id}`)
else if (tab === 'addon') await api.delete(`/cpanel/domains/addon/${id}`) else if (tab === 'addon') await api.delete(`/nixclient/domains/addon/${id}`)
else await api.delete(`/cpanel/domains/parked/${id}`) else await api.delete(`/nixclient/domains/parked/${id}`)
fetch() fetch()
} catch {} } catch {}
} }
@@ -20,7 +20,7 @@ export default function Email() {
const fetch = async () => { const fetch = async () => {
setLoading(true) setLoading(true)
try { try {
const [em, fw] = await Promise.all([api.get('/cpanel/email'), api.get('/cpanel/email/forwarders')]) const [em, fw] = await Promise.all([api.get('/nixclient/email'), api.get('/nixclient/email/forwarders')])
setAccounts(em.data.data) setAccounts(em.data.data)
setForwarders(fw.data.data) setForwarders(fw.data.data)
} finally { setLoading(false) } } finally { setLoading(false) }
@@ -31,7 +31,7 @@ export default function Email() {
const createAccount = async (e: React.FormEvent) => { const createAccount = async (e: React.FormEvent) => {
e.preventDefault(); setSaving(true); setError('') e.preventDefault(); setSaving(true); setError('')
try { try {
await api.post('/cpanel/email', form) await api.post('/nixclient/email', form)
setShowModal(false); fetch() setShowModal(false); fetch()
} catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } } catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') }
finally { setSaving(false) } finally { setSaving(false) }
@@ -39,18 +39,18 @@ export default function Email() {
const del = async (id: number) => { const del = async (id: number) => {
if (!confirm('Delete this email account?')) return if (!confirm('Delete this email account?')) return
try { await api.delete(`/cpanel/email/${id}`); fetch() } catch {} try { await api.delete(`/nixclient/email/${id}`); fetch() } catch {}
} }
const addForwarder = async (e: React.FormEvent) => { const addForwarder = async (e: React.FormEvent) => {
e.preventDefault(); setSaving(true); setError('') e.preventDefault(); setSaving(true); setError('')
try { await api.post('/cpanel/email/forwarders', fwdForm); setShowFwd(false); fetch() } try { await api.post('/nixclient/email/forwarders', fwdForm); setShowFwd(false); fetch() }
catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') }
finally { setSaving(false) } finally { setSaving(false) }
} }
const delFwd = async (id: number) => { const delFwd = async (id: number) => {
try { await api.delete(`/cpanel/email/forwarders/${id}`); fetch() } catch {} try { await api.delete(`/nixclient/email/forwarders/${id}`); fetch() } catch {}
} }
return ( return (
@@ -14,7 +14,7 @@ export default function FTP() {
const fetch = async () => { const fetch = async () => {
setLoading(true) setLoading(true)
try { const r = await api.get('/cpanel/ftp'); setAccounts(r.data.data) } try { const r = await api.get('/nixclient/ftp'); setAccounts(r.data.data) }
finally { setLoading(false) } finally { setLoading(false) }
} }
@@ -22,14 +22,14 @@ export default function FTP() {
const save = async (e: React.FormEvent) => { const save = async (e: React.FormEvent) => {
e.preventDefault(); setSaving(true); setError('') e.preventDefault(); setSaving(true); setError('')
try { await api.post('/cpanel/ftp', form); setShowModal(false); fetch() } try { await api.post('/nixclient/ftp', form); setShowModal(false); fetch() }
catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') }
finally { setSaving(false) } finally { setSaving(false) }
} }
const del = async (id: number) => { const del = async (id: number) => {
if (!confirm('Delete this FTP account?')) return if (!confirm('Delete this FTP account?')) return
try { await api.delete(`/cpanel/ftp/${id}`); fetch() } catch {} try { await api.delete(`/nixclient/ftp/${id}`); fetch() } catch {}
} }
return ( return (
@@ -19,7 +19,7 @@ export default function FileManager() {
const loadDir = async (p: string) => { const loadDir = async (p: string) => {
setLoading(true); setSelected(new Set()) setLoading(true); setSelected(new Set())
try { try {
const r = await api.get(`/cpanel/files?path=${encodeURIComponent(p)}`) const r = await api.get(`/nixclient/files?path=${encodeURIComponent(p)}`)
setFiles(r.data.data.files); setPath(r.data.data.path) setFiles(r.data.data.files); setPath(r.data.data.path)
} catch (e: unknown) { } catch (e: unknown) {
alert((e as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed to load directory') alert((e as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed to load directory')
@@ -35,7 +35,7 @@ export default function FileManager() {
const openFile = async (filePath: string) => { const openFile = async (filePath: string) => {
try { try {
const r = await api.get(`/cpanel/files/content?path=${encodeURIComponent(filePath)}`) const r = await api.get(`/nixclient/files/content?path=${encodeURIComponent(filePath)}`)
setEditing({ path: filePath, content: r.data.data.content }) setEditing({ path: filePath, content: r.data.data.content })
} catch (e: unknown) { } catch (e: unknown) {
alert((e as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Cannot open file') alert((e as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Cannot open file')
@@ -46,7 +46,7 @@ export default function FileManager() {
if (!editing) return if (!editing) return
setSaving(true) setSaving(true)
try { try {
await api.put('/cpanel/files/content', { path: editing.path, content: editing.content }) await api.put('/nixclient/files/content', { path: editing.path, content: editing.content })
setEditing(null) setEditing(null)
} catch (e: unknown) { } catch (e: unknown) {
alert((e as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Save failed') alert((e as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Save failed')
@@ -56,7 +56,7 @@ export default function FileManager() {
const deleteSelected = async () => { const deleteSelected = async () => {
if (!selected.size || !confirm(`Delete ${selected.size} item(s)?`)) return if (!selected.size || !confirm(`Delete ${selected.size} item(s)?`)) return
try { try {
await api.delete('/cpanel/files', { data: { paths: Array.from(selected) } }) await api.delete('/nixclient/files', { data: { paths: Array.from(selected) } })
loadDir(path) loadDir(path)
} catch {} } catch {}
} }
@@ -64,7 +64,7 @@ export default function FileManager() {
const createDir = async (e: React.FormEvent) => { const createDir = async (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
try { try {
await api.post('/cpanel/files/mkdir', { path: `${path}/${newDir}` }) await api.post('/nixclient/files/mkdir', { path: `${path}/${newDir}` })
setNewDir(''); setShowNewDir(false); loadDir(path) setNewDir(''); setShowNewDir(false); loadDir(path)
} catch (e: unknown) { } catch (e: unknown) {
alert((e as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') alert((e as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed')
@@ -27,7 +27,7 @@ export default function Login() {
<div className="w-12 h-12 rounded-2xl bg-sky-500 flex items-center justify-center mb-3"> <div className="w-12 h-12 rounded-2xl bg-sky-500 flex items-center justify-center mb-3">
<span className="text-white text-xl font-bold">N</span> <span className="text-white text-xl font-bold">N</span>
</div> </div>
<h1 className="text-white text-xl font-bold">NixPanel</h1> <h1 className="text-white text-xl font-bold">NixClient</h1>
<p className="text-[#64748b] text-sm mt-1">Hosting Control Panel</p> <p className="text-[#64748b] text-sm mt-1">Hosting Control Panel</p>
</div> </div>
@@ -49,7 +49,7 @@ export default function Login() {
</button> </button>
</form> </form>
<p className="text-center text-[#64748b] text-xs mt-4">NixPanel v0.1.0 · Port 2083</p> <p className="text-center text-[#64748b] text-xs mt-4">NixClient v0.2.0 · Port 2083</p>
</div> </div>
</div> </div>
) )
@@ -53,7 +53,7 @@ export default function Overview() {
const [stats, setStats] = useState<Stats | null>(null) const [stats, setStats] = useState<Stats | null>(null)
useEffect(() => { useEffect(() => {
api.get('/cpanel/stats').then(r => setStats(r.data.data)).catch(() => {}) api.get('/nixclient/stats').then(r => setStats(r.data.data)).catch(() => {})
}, []) }, [])
return ( return (
@@ -10,14 +10,14 @@ export default function SSL() {
const fetch = async () => { const fetch = async () => {
setLoading(true) setLoading(true)
try { const r = await api.get('/whm/ssl'); setCerts(r.data.data) } try { const r = await api.get('/nixserver/ssl'); setCerts(r.data.data) }
finally { setLoading(false) } finally { setLoading(false) }
} }
useEffect(() => { fetch() }, []) useEffect(() => { fetch() }, [])
const renew = async (id: number) => { const renew = async (id: number) => {
try { await api.post(`/whm/ssl/${id}/renew`); fetch() } try { await api.post(`/nixserver/ssl/${id}/renew`); fetch() }
catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Renewal failed') } catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Renewal failed') }
} }
@@ -34,7 +34,7 @@ export default function Stats() {
const fetch = async () => { const fetch = async () => {
setLoading(true) setLoading(true)
try { const r = await api.get('/cpanel/stats'); setStats(r.data.data) } try { const r = await api.get('/nixclient/stats'); setStats(r.data.data) }
finally { setLoading(false) } finally { setLoading(false) }
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@nixpanel/whm", "name": "@nixpanel/whm",
"version": "0.1.0", "version": "0.2.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -49,8 +49,8 @@ export default function Layout() {
</div> </div>
{sidebarOpen && ( {sidebarOpen && (
<div className="overflow-hidden"> <div className="overflow-hidden">
<p className="text-white text-sm font-semibold leading-tight">NixPanel</p> <p className="text-white text-sm font-semibold leading-tight">NixServer</p>
<p className="text-[#64748b] text-xs">WHM</p> <p className="text-[#64748b] text-xs">Admin Panel</p>
</div> </div>
)} )}
</div> </div>
@@ -44,7 +44,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const login = async (username: string, password: string) => { const login = async (username: string, password: string) => {
const { data } = await api.post('/auth/login', { username, password }) const { data } = await api.post('/auth/login', { username, password })
if (data.data.role !== 'admin' && data.data.role !== 'reseller') { if (data.data.role !== 'admin' && data.data.role !== 'reseller') {
throw new Error('Access denied — WHM is for administrators and resellers only') throw new Error('Access denied — NixServer is for administrators and resellers only')
} }
localStorage.setItem('access_token', data.data.accessToken) localStorage.setItem('access_token', data.data.accessToken)
localStorage.setItem('refresh_token', data.data.refreshToken) localStorage.setItem('refresh_token', data.data.refreshToken)
@@ -43,7 +43,7 @@ export default function Accounts() {
const params = new URLSearchParams({ page: String(page), limit: '25' }) const params = new URLSearchParams({ page: String(page), limit: '25' })
if (search) params.set('search', search) if (search) params.set('search', search)
if (status) params.set('status', status) if (status) params.set('status', status)
const r = await api.get(`/whm/accounts?${params}`) const r = await api.get(`/nixserver/accounts?${params}`)
setData(r.data.data) setData(r.data.data)
} finally { } finally {
setLoading(false) setLoading(false)
@@ -56,9 +56,9 @@ export default function Accounts() {
setActionMenu(null) setActionMenu(null)
if (act === 'terminate' && !confirm('Terminate this account? This cannot be undone.')) return if (act === 'terminate' && !confirm('Terminate this account? This cannot be undone.')) return
try { try {
if (act === 'suspend') await api.post(`/whm/accounts/${id}/suspend`) if (act === 'suspend') await api.post(`/nixserver/accounts/${id}/suspend`)
else if (act === 'unsuspend') await api.post(`/whm/accounts/${id}/unsuspend`) else if (act === 'unsuspend') await api.post(`/nixserver/accounts/${id}/unsuspend`)
else await api.delete(`/whm/accounts/${id}`) else await api.delete(`/nixserver/accounts/${id}`)
fetch() fetch()
} catch (e: unknown) { } catch (e: unknown) {
alert((e as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Action failed') alert((e as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Action failed')
@@ -16,7 +16,7 @@ export default function Backup() {
const fetch = async () => { const fetch = async () => {
setLoading(true) setLoading(true)
try { try {
const [b, a] = await Promise.all([api.get('/whm/backup/list'), api.get('/whm/accounts?limit=100')]) const [b, a] = await Promise.all([api.get('/nixserver/backup/list'), api.get('/nixserver/accounts?limit=100')])
setBackups(b.data.data) setBackups(b.data.data)
setAccounts(a.data.data.items) setAccounts(a.data.data.items)
} finally { setLoading(false) } } finally { setLoading(false) }
@@ -27,7 +27,7 @@ export default function Backup() {
const backupAccount = async () => { const backupAccount = async () => {
if (!selectedAccount) return alert('Select an account') if (!selectedAccount) return alert('Select an account')
setBacking(true) setBacking(true)
try { await api.post(`/whm/backup/account/${selectedAccount}`); fetch() } try { await api.post(`/nixserver/backup/account/${selectedAccount}`); fetch() }
catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Backup failed') } catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Backup failed') }
finally { setBacking(false) } finally { setBacking(false) }
} }
@@ -35,14 +35,14 @@ export default function Backup() {
const backupServer = async () => { const backupServer = async () => {
if (!confirm('Start a full server backup? This may take a while.')) return if (!confirm('Start a full server backup? This may take a while.')) return
setBacking(true) setBacking(true)
try { await api.post('/whm/backup/server'); fetch() } try { await api.post('/nixserver/backup/server'); fetch() }
catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Backup failed') } catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Backup failed') }
finally { setBacking(false) } finally { setBacking(false) }
} }
const del = async (path: string) => { const del = async (path: string) => {
if (!confirm('Delete this backup?')) return if (!confirm('Delete this backup?')) return
try { await api.delete('/whm/backup', { data: { path } }); fetch() } try { await api.delete('/nixserver/backup', { data: { path } }); fetch() }
catch {} catch {}
} }
@@ -15,7 +15,7 @@ export default function CreateAccount() {
}) })
useEffect(() => { useEffect(() => {
api.get('/whm/packages').then(r => setPackages(r.data.data)).catch(() => {}) api.get('/nixserver/packages').then(r => setPackages(r.data.data)).catch(() => {})
}, []) }, [])
const set = (k: keyof typeof form) => (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => const set = (k: keyof typeof form) => (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) =>
@@ -26,7 +26,7 @@ export default function CreateAccount() {
setError('') setError('')
setLoading(true) setLoading(true)
try { try {
await api.post('/whm/accounts', { await api.post('/nixserver/accounts', {
...form, ...form,
packageId: form.packageId ? parseInt(form.packageId, 10) : undefined, packageId: form.packageId ? parseInt(form.packageId, 10) : undefined,
}) })
@@ -56,7 +56,7 @@ export default function CreateAccount() {
{ key: 'username', label: 'Username', type: 'text', placeholder: 'john', hint: 'Lowercase letters, numbers, hyphens' }, { key: 'username', label: 'Username', type: 'text', placeholder: 'john', hint: 'Lowercase letters, numbers, hyphens' },
{ key: 'domain', label: 'Primary Domain', type: 'text', placeholder: 'example.com' }, { key: 'domain', label: 'Primary Domain', type: 'text', placeholder: 'example.com' },
{ key: 'email', label: 'Contact Email', type: 'email', placeholder: '[email protected]' }, { key: 'email', label: 'Contact Email', type: 'email', placeholder: '[email protected]' },
{ key: 'password', label: 'cPanel Password', type: 'password', placeholder: '••••••••', hint: 'Min 8 characters' }, { key: 'password', label: 'NixClient Password', type: 'password', placeholder: '••••••••', hint: 'Min 8 characters' },
].map(({ key, label, type, placeholder, hint }) => ( ].map(({ key, label, type, placeholder, hint }) => (
<div key={key}> <div key={key}>
<label className="block text-[#94a3b8] text-xs font-medium mb-1.5">{label}</label> <label className="block text-[#94a3b8] text-xs font-medium mb-1.5">{label}</label>
@@ -16,13 +16,13 @@ export default function DNS() {
const fetchZones = async () => { const fetchZones = async () => {
setLoading(true) setLoading(true)
try { const r = await api.get('/whm/dns'); setZones(r.data.data) } try { const r = await api.get('/nixserver/dns'); setZones(r.data.data) }
finally { setLoading(false) } finally { setLoading(false) }
} }
const loadZone = async (zone: Zone) => { const loadZone = async (zone: Zone) => {
setSelected(zone) setSelected(zone)
const r = await api.get(`/whm/dns/${zone.id}`) const r = await api.get(`/nixserver/dns/${zone.id}`)
setRecords(r.data.data.records ?? []) setRecords(r.data.data.records ?? [])
} }
@@ -32,7 +32,7 @@ export default function DNS() {
e.preventDefault(); if (!selected) return e.preventDefault(); if (!selected) return
setSaving(true) setSaving(true)
try { try {
await api.post(`/whm/dns/${selected.id}/records`, { ...form, priority: form.priority ? parseInt(form.priority, 10) : undefined }) await api.post(`/nixserver/dns/${selected.id}/records`, { ...form, priority: form.priority ? parseInt(form.priority, 10) : undefined })
setShowForm(false) setShowForm(false)
loadZone(selected) loadZone(selected)
} catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } } catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') }
@@ -41,7 +41,7 @@ export default function DNS() {
const deleteRecord = async (recordId: number) => { const deleteRecord = async (recordId: number) => {
if (!selected || !confirm('Delete this DNS record?')) return if (!selected || !confirm('Delete this DNS record?')) return
try { await api.delete(`/whm/dns/${selected.id}/records/${recordId}`); loadZone(selected) } try { await api.delete(`/nixserver/dns/${selected.id}/records/${recordId}`); loadZone(selected) }
catch {} catch {}
} }
@@ -38,8 +38,8 @@ export default function Dashboard() {
const wsRef = useRef<WebSocket | null>(null) const wsRef = useRef<WebSocket | null>(null)
useEffect(() => { useEffect(() => {
api.get('/whm/accounts/stats').then(r => setAccountStats(r.data.data)).catch(() => {}) api.get('/nixserver/accounts/stats').then(r => setAccountStats(r.data.data)).catch(() => {})
api.get('/whm/system/stats').then(r => setStats(r.data.data)).catch(() => {}) api.get('/nixserver/system/stats').then(r => setStats(r.data.data)).catch(() => {})
// WebSocket live stats // WebSocket live stats
const token = localStorage.getItem('access_token') const token = localStorage.getItem('access_token')
@@ -16,7 +16,7 @@ export default function Email() {
const fetch = async () => { const fetch = async () => {
setLoading(true) setLoading(true)
try { try {
const [em, acc] = await Promise.all([api.get('/whm/email'), api.get('/whm/accounts?limit=100')]) const [em, acc] = await Promise.all([api.get('/nixserver/email'), api.get('/nixserver/accounts?limit=100')])
setAccounts(em.data.data) setAccounts(em.data.data)
setHosting(acc.data.data.items) setHosting(acc.data.data.items)
} finally { setLoading(false) } } finally { setLoading(false) }
@@ -27,7 +27,7 @@ export default function Email() {
const save = async (e: React.FormEvent) => { const save = async (e: React.FormEvent) => {
e.preventDefault(); setSaving(true); setError('') e.preventDefault(); setSaving(true); setError('')
try { try {
await api.post('/whm/email', { ...form, accountId: parseInt(form.accountId, 10), quotaMb: form.quotaMb }) await api.post('/nixserver/email', { ...form, accountId: parseInt(form.accountId, 10), quotaMb: form.quotaMb })
setShowModal(false); fetch() setShowModal(false); fetch()
} catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } } catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') }
finally { setSaving(false) } finally { setSaving(false) }
@@ -35,7 +35,7 @@ export default function Email() {
const del = async (id: number, address: string) => { const del = async (id: number, address: string) => {
if (!confirm(`Delete ${address}?`)) return if (!confirm(`Delete ${address}?`)) return
try { await api.delete(`/whm/email/${id}`); fetch() } try { await api.delete(`/nixserver/email/${id}`); fetch() }
catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') }
} }
@@ -34,7 +34,7 @@ export default function Login() {
<div className="w-12 h-12 rounded-2xl bg-indigo-600 flex items-center justify-center mb-3"> <div className="w-12 h-12 rounded-2xl bg-indigo-600 flex items-center justify-center mb-3">
<Shield size={24} className="text-white" /> <Shield size={24} className="text-white" />
</div> </div>
<h1 className="text-white text-xl font-bold">NixPanel WHM</h1> <h1 className="text-white text-xl font-bold">NixServer</h1>
<p className="text-[#64748b] text-sm mt-1">Web Host Manager</p> <p className="text-[#64748b] text-sm mt-1">Web Host Manager</p>
</div> </div>
@@ -81,7 +81,7 @@ export default function Login() {
</form> </form>
<p className="text-center text-[#64748b] text-xs mt-4"> <p className="text-center text-[#64748b] text-xs mt-4">
NixPanel v0.1.0 · WHM Port 2087 NixServer v0.2.0 · Port 2087
</p> </p>
</div> </div>
</div> </div>
@@ -13,7 +13,7 @@ export default function PHP() {
const fetch = async () => { const fetch = async () => {
setLoading(true) setLoading(true)
try { try {
const [v, a] = await Promise.all([api.get('/whm/php/versions'), api.get('/whm/php/accounts')]) const [v, a] = await Promise.all([api.get('/nixserver/php/versions'), api.get('/nixserver/php/accounts')])
setVersions(v.data.data) setVersions(v.data.data)
setAccounts(a.data.data) setAccounts(a.data.data)
} finally { setLoading(false) } } finally { setLoading(false) }
@@ -23,7 +23,7 @@ export default function PHP() {
const changeVersion = async (accountId: number, phpVersion: string) => { const changeVersion = async (accountId: number, phpVersion: string) => {
try { try {
await api.put(`/whm/php/accounts/${accountId}`, { phpVersion }) await api.put(`/nixserver/php/accounts/${accountId}`, { phpVersion })
fetch() fetch()
} catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } } catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') }
} }
@@ -27,7 +27,7 @@ export default function Packages() {
const fetch = async () => { const fetch = async () => {
setLoading(true) setLoading(true)
try { const r = await api.get('/whm/packages'); setPackages(r.data.data) } try { const r = await api.get('/nixserver/packages'); setPackages(r.data.data) }
finally { setLoading(false) } finally { setLoading(false) }
} }
@@ -44,8 +44,8 @@ export default function Packages() {
const save = async (e: React.FormEvent) => { const save = async (e: React.FormEvent) => {
e.preventDefault(); setSaving(true); setError('') e.preventDefault(); setSaving(true); setError('')
try { try {
if (editing) await api.put(`/whm/packages/${editing.id}`, form) if (editing) await api.put(`/nixserver/packages/${editing.id}`, form)
else await api.post('/whm/packages', form) else await api.post('/nixserver/packages', form)
setShowModal(false); fetch() setShowModal(false); fetch()
} catch (err: unknown) { } catch (err: unknown) {
setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Save failed') setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Save failed')
@@ -54,7 +54,7 @@ export default function Packages() {
const del = async (p: Package) => { const del = async (p: Package) => {
if (!confirm(`Delete package "${p.name}"?`)) return if (!confirm(`Delete package "${p.name}"?`)) return
try { await api.delete(`/whm/packages/${p.id}`); fetch() } try { await api.delete(`/nixserver/packages/${p.id}`); fetch() }
catch (err: unknown) { catch (err: unknown) {
alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Delete failed') alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Delete failed')
} }
@@ -17,7 +17,7 @@ export default function Resellers() {
setLoading(true) setLoading(true)
try { try {
const params = search ? `?search=${encodeURIComponent(search)}` : '' const params = search ? `?search=${encodeURIComponent(search)}` : ''
const r = await api.get(`/whm/resellers${params}`) const r = await api.get(`/nixserver/resellers${params}`)
setItems(r.data.data.items ?? r.data.data) setItems(r.data.data.items ?? r.data.data)
} finally { setLoading(false) } } finally { setLoading(false) }
}, [search]) }, [search])
@@ -26,7 +26,7 @@ export default function Resellers() {
const save = async (e: React.FormEvent) => { const save = async (e: React.FormEvent) => {
e.preventDefault(); setSaving(true); setError('') e.preventDefault(); setSaving(true); setError('')
try { await api.post('/whm/resellers', form); setShowModal(false); fetch() } try { await api.post('/nixserver/resellers', form); setShowModal(false); fetch() }
catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') }
finally { setSaving(false) } finally { setSaving(false) }
} }
@@ -34,9 +34,9 @@ export default function Resellers() {
const act = async (id: number, action: 'suspend' | 'unsuspend' | 'delete') => { const act = async (id: number, action: 'suspend' | 'unsuspend' | 'delete') => {
if (action === 'delete' && !confirm('Delete this reseller?')) return if (action === 'delete' && !confirm('Delete this reseller?')) return
try { try {
if (action === 'suspend') await api.post(`/whm/resellers/${id}/suspend`) if (action === 'suspend') await api.post(`/nixserver/resellers/${id}/suspend`)
else if (action === 'unsuspend') await api.post(`/whm/resellers/${id}/unsuspend`) else if (action === 'unsuspend') await api.post(`/nixserver/resellers/${id}/unsuspend`)
else await api.delete(`/whm/resellers/${id}`) else await api.delete(`/nixserver/resellers/${id}`)
fetch() fetch()
} catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } } catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') }
} }
@@ -23,7 +23,7 @@ export default function SSL() {
const fetch = async () => { const fetch = async () => {
setLoading(true) setLoading(true)
try { try {
const [sl, acc] = await Promise.all([api.get('/whm/ssl'), api.get('/whm/accounts?limit=100')]) const [sl, acc] = await Promise.all([api.get('/nixserver/ssl'), api.get('/nixserver/accounts?limit=100')])
setCerts(sl.data.data) setCerts(sl.data.data)
setHosting(acc.data.data.items) setHosting(acc.data.data.items)
} finally { setLoading(false) } } finally { setLoading(false) }
@@ -34,20 +34,20 @@ export default function SSL() {
const issue = async (e: React.FormEvent) => { const issue = async (e: React.FormEvent) => {
e.preventDefault(); setSaving(true); setError('') e.preventDefault(); setSaving(true); setError('')
try { try {
await api.post('/whm/ssl/issue', { ...form, accountId: parseInt(form.accountId, 10) }) await api.post('/nixserver/ssl/issue', { ...form, accountId: parseInt(form.accountId, 10) })
setShowModal(false); fetch() setShowModal(false); fetch()
} catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } } catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') }
finally { setSaving(false) } finally { setSaving(false) }
} }
const renew = async (id: number) => { const renew = async (id: number) => {
try { await api.post(`/whm/ssl/${id}/renew`); fetch() } try { await api.post(`/nixserver/ssl/${id}/renew`); fetch() }
catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Renewal failed') } catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Renewal failed') }
} }
const revoke = async (id: number) => { const revoke = async (id: number) => {
if (!confirm('Revoke this certificate?')) return if (!confirm('Revoke this certificate?')) return
try { await api.post(`/whm/ssl/${id}/revoke`); fetch() } try { await api.post(`/nixserver/ssl/${id}/revoke`); fetch() }
catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Revoke failed') } catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Revoke failed') }
} }
@@ -22,8 +22,8 @@ export default function Security() {
setLoading(true) setLoading(true)
try { try {
const [b, e] = await Promise.all([ const [b, e] = await Promise.all([
api.get('/whm/security/blocked-ips'), api.get('/nixserver/security/blocked-ips'),
api.get('/whm/security/events'), api.get('/nixserver/security/events'),
]) ])
setBlocked(b.data.data) setBlocked(b.data.data)
setEvents(e.data.data.items ?? e.data.data) setEvents(e.data.data.items ?? e.data.data)
@@ -34,17 +34,17 @@ export default function Security() {
const block = async (e: React.FormEvent) => { const block = async (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
try { await api.post('/whm/security/blocked-ips', { ip: newIp, reason }); setNewIp(''); setReason(''); fetch() } try { await api.post('/nixserver/security/blocked-ips', { ip: newIp, reason }); setNewIp(''); setReason(''); fetch() }
catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') }
} }
const unblock = async (id: number) => { const unblock = async (id: number) => {
try { await api.delete(`/whm/security/blocked-ips/${id}`); fetch() } try { await api.delete(`/nixserver/security/blocked-ips/${id}`); fetch() }
catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') }
} }
const resolve = async (id: number) => { const resolve = async (id: number) => {
try { await api.post(`/whm/security/events/${id}/resolve`); fetch() } try { await api.post(`/nixserver/security/events/${id}/resolve`); fetch() }
catch {} catch {}
} }
@@ -18,7 +18,7 @@ export default function Services() {
const fetch = async () => { const fetch = async () => {
setLoading(true) setLoading(true)
try { const r = await api.get('/whm/services'); setServices(r.data.data) } try { const r = await api.get('/nixserver/services'); setServices(r.data.data) }
finally { setLoading(false) } finally { setLoading(false) }
} }
@@ -26,7 +26,7 @@ export default function Services() {
const act = async (name: string, action: string) => { const act = async (name: string, action: string) => {
setActing(name + action) setActing(name + action)
try { await api.post(`/whm/services/${name}`, { action }); fetch() } try { await api.post(`/nixserver/services/${name}`, { action }); fetch() }
catch (e: unknown) { alert((e as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } catch (e: unknown) { alert((e as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') }
finally { setActing(null) } finally { setActing(null) }
} }
@@ -10,7 +10,7 @@ export default function Settings() {
const fetch = async () => { const fetch = async () => {
setLoading(true) setLoading(true)
try { const r = await api.get('/whm/system/settings'); setSettings(r.data.data) } try { const r = await api.get('/nixserver/system/settings'); setSettings(r.data.data) }
finally { setLoading(false) } finally { setLoading(false) }
} }
@@ -18,7 +18,7 @@ export default function Settings() {
const save = async (e: React.FormEvent) => { const save = async (e: React.FormEvent) => {
e.preventDefault(); setSaving(true) e.preventDefault(); setSaving(true)
try { await api.put('/whm/system/settings', settings); setSaved(true); setTimeout(() => setSaved(false), 2000) } try { await api.put('/nixserver/system/settings', settings); setSaved(true); setTimeout(() => setSaved(false), 2000) }
finally { setSaving(false) } finally { setSaving(false) }
} }
@@ -14,8 +14,8 @@ export default function System() {
setLoading(true) setLoading(true)
try { try {
const [infoR, procR] = await Promise.all([ const [infoR, procR] = await Promise.all([
api.get('/whm/system/info'), api.get('/nixserver/system/info'),
api.get('/whm/system/processes'), api.get('/nixserver/system/processes'),
]) ])
setInfo(infoR.data.data) setInfo(infoR.data.data)
setProcesses(procR.data.data) setProcesses(procR.data.data)
@@ -14,9 +14,9 @@ export default function WebServer() {
setLoading(true) setLoading(true)
try { try {
const [s, v, l] = await Promise.all([ const [s, v, l] = await Promise.all([
api.get('/whm/webserver/status'), api.get('/nixserver/webserver/status'),
api.get('/whm/webserver/vhosts'), api.get('/nixserver/webserver/vhosts'),
api.get(`/whm/webserver/logs/${logType}`), api.get(`/nixserver/webserver/logs/${logType}`),
]) ])
setStatus(s.data.data); setVhosts(v.data.data); setLogs(l.data.data) setStatus(s.data.data); setVhosts(v.data.data); setLogs(l.data.data)
} finally { setLoading(false) } } finally { setLoading(false) }
@@ -25,12 +25,12 @@ export default function WebServer() {
useEffect(() => { fetch() }, [logType]) useEffect(() => { fetch() }, [logType])
const testConfig = async () => { const testConfig = async () => {
const r = await api.post('/whm/webserver/nginx/test') const r = await api.post('/nixserver/webserver/nginx/test')
setTestResult(r.data.data.output) setTestResult(r.data.data.output)
} }
const reload = async () => { const reload = async () => {
try { await api.post('/whm/webserver/nginx/reload'); fetch() } try { await api.post('/nixserver/webserver/nginx/reload'); fetch() }
catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Reload failed') } catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Reload failed') }
} }
+7 -7
View File
@@ -1,18 +1,18 @@
{ {
"name": "nixpanel-server", "name": "nixpanel-server",
"version": "0.1.0", "version": "0.2.0",
"private": true, "private": true,
"workspaces": [ "workspaces": [
"server", "server",
"whm", "nixserver",
"cpanel" "nixclient"
], ],
"scripts": { "scripts": {
"dev": "concurrently \"npm run dev --workspace=server\" \"npm run dev --workspace=whm\" \"npm run dev --workspace=cpanel\"", "dev": "concurrently \"npm run dev --workspace=server\" \"npm run dev --workspace=nixserver\" \"npm run dev --workspace=nixclient\"",
"dev:server": "npm run dev --workspace=server", "dev:server": "npm run dev --workspace=server",
"dev:whm": "npm run dev --workspace=whm", "dev:nixserver": "npm run dev --workspace=nixserver",
"dev:cpanel": "npm run dev --workspace=cpanel", "dev:nixclient": "npm run dev --workspace=nixclient",
"build": "npm run build --workspace=server && npm run build --workspace=whm && npm run build --workspace=cpanel", "build": "npm run build --workspace=server && npm run build --workspace=nixserver && npm run build --workspace=nixclient",
"db:migrate": "npm run db:migrate --workspace=server", "db:migrate": "npm run db:migrate --workspace=server",
"db:studio": "npm run db:studio --workspace=server" "db:studio": "npm run db:studio --workspace=server"
}, },
+18 -18
View File
@@ -30,8 +30,8 @@ EOF
echo -e "${RESET}" echo -e "${RESET}"
echo -e " Version : ${BOLD}0.1.0${RESET}" echo -e " Version : ${BOLD}0.1.0${RESET}"
echo -e " GitHub : ${BOLD}https://github.com/NixReaper/nixpanel-server${RESET}" echo -e " GitHub : ${BOLD}https://github.com/NixReaper/nixpanel-server${RESET}"
echo -e " WHM : ${BOLD}https://<your-ip>:2087${RESET}" echo -e " NixServer: ${BOLD}https://<your-ip>:2087${RESET}"
echo -e " cPanel : ${BOLD}https://<your-ip>:2083${RESET}" echo -e " NixClient: ${BOLD}https://<your-ip>:2083${RESET}"
echo "" echo ""
# ── Root check ──────────────────────────────────────────────────────────────── # ── Root check ────────────────────────────────────────────────────────────────
@@ -341,8 +341,8 @@ JWT_REFRESH_EXPIRES_IN="7d"
# Server # Server
NODE_ENV="production" NODE_ENV="production"
WHM_PORT=2087 NIXSERVER_PORT=2087
CPANEL_PORT=2083 NIXCLIENT_PORT=2083
HOST="0.0.0.0" HOST="0.0.0.0"
# Admin credentials (used during initial seed only) # Admin credentials (used during initial seed only)
@@ -398,11 +398,11 @@ ADMIN_EMAIL="${ADMIN_EMAIL}" \
success "Default admin, packages, and settings seeded" success "Default admin, packages, and settings seeded"
# ── Build frontends ─────────────────────────────────────────────────────────── # ── Build frontends ───────────────────────────────────────────────────────────
step "Building WHM + cPanel frontends" step "Building NixServer + NixClient frontends"
cd "$INSTALL_DIR" cd "$INSTALL_DIR"
npm run build --workspace=whm --silent npm run build --workspace=nixserver --silent
npm run build --workspace=cpanel --silent npm run build --workspace=nixclient --silent
success "Frontends built" success "Frontends built"
# ── systemd service ─────────────────────────────────────────────────────────── # ── systemd service ───────────────────────────────────────────────────────────
@@ -453,13 +453,13 @@ fi
cat > /etc/nginx/sites-available/nixpanel.conf <<NGINX cat > /etc/nginx/sites-available/nixpanel.conf <<NGINX
# NixPanel — generated by installer # NixPanel — generated by installer
# WHM admin panel on port 2087 # NixServer admin panel on port 2087
server { server {
listen 2087; listen 2087;
server_name _; server_name _;
# Serve pre-built WHM React app # Serve pre-built NixServer React app
root ${INSTALL_DIR}/whm/dist; root ${INSTALL_DIR}/nixserver/dist;
index index.html; index index.html;
location / { location / {
@@ -485,12 +485,12 @@ server {
} }
} }
# cPanel user panel on port 2083 # NixClient user panel on port 2083
server { server {
listen 2083; listen 2083;
server_name _; server_name _;
root ${INSTALL_DIR}/cpanel/dist; root ${INSTALL_DIR}/nixclient/dist;
index index.html; index index.html;
location / { location / {
@@ -538,8 +538,8 @@ if [[ "$PKG_MGR" == "apt" ]]; then
ufw allow 22/tcp comment "SSH" >/dev/null ufw allow 22/tcp comment "SSH" >/dev/null
ufw allow 80/tcp comment "HTTP" >/dev/null ufw allow 80/tcp comment "HTTP" >/dev/null
ufw allow 443/tcp comment "HTTPS" >/dev/null ufw allow 443/tcp comment "HTTPS" >/dev/null
ufw allow 2087/tcp comment "WHM" >/dev/null ufw allow 2087/tcp comment "NixServer" >/dev/null
ufw allow 2083/tcp comment "cPanel" >/dev/null ufw allow 2083/tcp comment "NixClient" >/dev/null
ufw allow 21/tcp comment "FTP" >/dev/null ufw allow 21/tcp comment "FTP" >/dev/null
ufw allow 25/tcp comment "SMTP" >/dev/null ufw allow 25/tcp comment "SMTP" >/dev/null
ufw allow 143/tcp comment "IMAP" >/dev/null ufw allow 143/tcp comment "IMAP" >/dev/null
@@ -637,12 +637,12 @@ echo -e "${BOLD}${GREEN}╔═════════════════
echo -e "${BOLD}${GREEN}║ NixPanel Installation Complete! ║${RESET}" echo -e "${BOLD}${GREEN}║ NixPanel Installation Complete! ║${RESET}"
echo -e "${BOLD}${GREEN}╚══════════════════════════════════════════════════════╝${RESET}" echo -e "${BOLD}${GREEN}╚══════════════════════════════════════════════════════╝${RESET}"
echo "" echo ""
echo -e " ${BOLD}WHM (Admin Panel)${RESET}" echo -e " ${BOLD}NixServer (Admin Panel)${RESET}"
echo -e " URL : ${CYAN}http://${SERVER_IP}:2087${RESET}" echo -e " URL : ${CYAN}http://${SERVER_IP}:2087${RESET}"
echo -e " Username : ${BOLD}${ADMIN_USER}${RESET}" echo -e " Username : ${BOLD}${ADMIN_USER}${RESET}"
echo -e " Password : ${BOLD}${ADMIN_PASS}${RESET}" echo -e " Password : ${BOLD}${ADMIN_PASS}${RESET}"
echo "" echo ""
echo -e " ${BOLD}cPanel (User Panel)${RESET}" echo -e " ${BOLD}NixClient (User Panel)${RESET}"
echo -e " URL : ${CYAN}http://${SERVER_IP}:2083${RESET}" echo -e " URL : ${CYAN}http://${SERVER_IP}:2083${RESET}"
echo "" echo ""
echo -e " ${BOLD}Database${RESET}" echo -e " ${BOLD}Database${RESET}"
@@ -671,8 +671,8 @@ CRED_FILE="/root/.nixpanel_credentials"
cat > "$CRED_FILE" <<CREDS cat > "$CRED_FILE" <<CREDS
NixPanel Credentials — $(date -u '+%Y-%m-%d %H:%M:%S UTC') NixPanel Credentials — $(date -u '+%Y-%m-%d %H:%M:%S UTC')
========================================================== ==========================================================
WHM URL : http://${SERVER_IP}:2087 NixServer URL : http://${SERVER_IP}:2087
cPanel URL : http://${SERVER_IP}:2083 NixClient URL : http://${SERVER_IP}:2083
Admin User : ${ADMIN_USER} Admin User : ${ADMIN_USER}
Admin Pass : ${ADMIN_PASS} Admin Pass : ${ADMIN_PASS}
Admin Email: ${ADMIN_EMAIL} Admin Email: ${ADMIN_EMAIL}
+2 -2
View File
@@ -30,8 +30,8 @@ success "Migrations applied"
step "Rebuilding frontends" step "Rebuilding frontends"
cd "$INSTALL_DIR" cd "$INSTALL_DIR"
npm run build --workspace=whm --silent npm run build --workspace=nixserver --silent
npm run build --workspace=cpanel --silent npm run build --workspace=nixclient --silent
success "Frontends rebuilt" success "Frontends rebuilt"
step "Rebuilding server" step "Rebuilding server"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@nixpanel/server", "name": "@nixpanel/server",
"version": "0.1.0", "version": "0.2.0",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",
+8 -8
View File
@@ -9,8 +9,8 @@ import path from 'path'
import { fileURLToPath } from 'url' import { fileURLToPath } from 'url'
import { config } from './config.js' import { config } from './config.js'
import authRoutes from './routes/auth/index.js' import authRoutes from './routes/auth/index.js'
import whmRoutes from './routes/whm/index.js' import nixserverRoutes from './routes/nixserver/index.js'
import cpanelRoutes from './routes/cpanel/index.js' import nixclientRoutes from './routes/nixclient/index.js'
import { getSystemStats } from './core/system.js' import { getSystemStats } from './core/system.js'
const __dirname = path.dirname(fileURLToPath(import.meta.url)) const __dirname = path.dirname(fileURLToPath(import.meta.url))
@@ -59,21 +59,21 @@ export async function buildApp() {
// ─── Static files ──────────────────────────────────────────────────────────── // ─── Static files ────────────────────────────────────────────────────────────
// WHM frontend served from /whm // NixServer frontend
const whmDist = path.resolve(__dirname, '../../whm/dist') const whmDist = path.resolve(__dirname, '../../nixserver/dist')
const cpanelDist = path.resolve(__dirname, '../../cpanel/dist') const cpanelDist = path.resolve(__dirname, '../../nixclient/dist')
// ─── API Routes ────────────────────────────────────────────────────────────── // ─── API Routes ──────────────────────────────────────────────────────────────
fastify.register(authRoutes, { prefix: '/api/auth' }) fastify.register(authRoutes, { prefix: '/api/auth' })
fastify.register(whmRoutes, { prefix: '/api/whm' }) fastify.register(nixserverRoutes, { prefix: '/api/nixserver' })
fastify.register(cpanelRoutes, { prefix: '/api/cpanel' }) fastify.register(nixclientRoutes, { prefix: '/api/nixclient' })
// ─── Health check ───────────────────────────────────────────────────────────── // ─── Health check ─────────────────────────────────────────────────────────────
fastify.get('/api/health', async () => ({ fastify.get('/api/health', async () => ({
status: 'ok', status: 'ok',
version: '0.1.0', version: '0.2.0',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
})) }))
+2 -2
View File
@@ -16,8 +16,8 @@ export const config = {
server: { server: {
host: optional('HOST', '0.0.0.0'), host: optional('HOST', '0.0.0.0'),
whmPort: parseInt(optional('WHM_PORT', '2087'), 10), nixserverPort: parseInt(optional('NIXSERVER_PORT', '2087'), 10),
cpanelPort: parseInt(optional('CPANEL_PORT', '2083'), 10), nixclientPort: parseInt(optional('NIXCLIENT_PORT', '2083'), 10),
}, },
db: { db: {
+1 -1
View File
@@ -45,7 +45,7 @@ $TTL ${ttl}
www IN A ${ip} www IN A ${ip}
mail IN A ${ip} mail IN A ${ip}
ftp IN A ${ip} ftp IN A ${ip}
cpanel IN A ${ip} nixclient IN A ${ip}
webmail IN A ${ip} webmail IN A ${ip}
; MX record ; MX record
+4 -4
View File
@@ -13,10 +13,10 @@ async function start() {
app = await buildApp() app = await buildApp()
// WHM server on port 2087 // NixServer on port 2087
await app.listen({ port: config.server.whmPort, host: config.server.host }) await app.listen({ port: config.server.nixserverPort, host: config.server.host })
console.log(`NixPanel WHM running on port ${config.server.whmPort}`) console.log(`NixServer running on port ${config.server.nixserverPort}`)
console.log(`NixPanel cPanel API running on port ${config.server.whmPort} (/api/cpanel)`) console.log(`NixClient API running on port ${config.server.nixserverPort} (/api/cpanel)`)
} catch (err) { } catch (err) {
console.error('Failed to start server:', err) console.error('Failed to start server:', err)
+2 -2
View File
@@ -37,7 +37,7 @@ function signTokens(
export default async function authRoutes(fastify: FastifyInstance) { export default async function authRoutes(fastify: FastifyInstance) {
/** /**
* POST /api/auth/login * POST /api/auth/login
* Login for admin, reseller, or cPanel user. * Login for admin, reseller, or NixClient user.
* Body: { username, password } * Body: { username, password }
* Returns: { accessToken, refreshToken, role, user } * Returns: { accessToken, refreshToken, role, user }
*/ */
@@ -111,7 +111,7 @@ export default async function authRoutes(fastify: FastifyInstance) {
}) })
} }
// Try hosting account (cPanel user) // Try hosting account (NixClient user)
const account = await prisma.account.findUnique({ where: { username } }) const account = await prisma.account.findUnique({ where: { username } })
if (account) { if (account) {
if (account.status !== 'active') { if (account.status !== 'active') {
@@ -12,7 +12,7 @@ function mysqlEscape(s: string): string {
export default async function databaseRoutes(fastify: FastifyInstance) { export default async function databaseRoutes(fastify: FastifyInstance) {
const preHandler = [requireAuth] const preHandler = [requireAuth]
// GET /api/cpanel/databases // GET /api/nixclient/databases
fastify.get('/', { preHandler }, async (request, reply) => { fastify.get('/', { preHandler }, async (request, reply) => {
const user = request.user! const user = request.user!
const accountId = user.role === 'user' ? user.sub : parseInt((request.query as Record<string, string>).accountId ?? '0', 10) const accountId = user.role === 'user' ? user.sub : parseInt((request.query as Record<string, string>).accountId ?? '0', 10)
@@ -25,7 +25,7 @@ export default async function databaseRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: databases }) return reply.send({ success: true, data: databases })
}) })
// POST /api/cpanel/databases // POST /api/nixclient/databases
fastify.post('/', { preHandler }, async (request, reply) => { fastify.post('/', { preHandler }, async (request, reply) => {
const user = request.user! const user = request.user!
const accountId = user.role === 'user' ? user.sub : parseInt((request.body as Record<string, string>).accountId ?? '0', 10) const accountId = user.role === 'user' ? user.sub : parseInt((request.body as Record<string, string>).accountId ?? '0', 10)
@@ -74,7 +74,7 @@ export default async function databaseRoutes(fastify: FastifyInstance) {
return reply.code(201).send({ success: true, data: db }) return reply.code(201).send({ success: true, data: db })
}) })
// DELETE /api/cpanel/databases/:id // DELETE /api/nixclient/databases/:id
fastify.delete('/:id', { preHandler }, async (request, reply) => { fastify.delete('/:id', { preHandler }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
const user = request.user! const user = request.user!
@@ -93,7 +93,7 @@ export default async function databaseRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { message: `Database ${db.name} deleted` } }) return reply.send({ success: true, data: { message: `Database ${db.name} deleted` } })
}) })
// POST /api/cpanel/databases/:id/users // POST /api/nixclient/databases/:id/users
fastify.post('/:id/users', { preHandler }, async (request, reply) => { fastify.post('/:id/users', { preHandler }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
const user = request.user! const user = request.user!
@@ -139,7 +139,7 @@ export default async function databaseRoutes(fastify: FastifyInstance) {
return reply.code(201).send({ success: true, data: safe }) return reply.code(201).send({ success: true, data: safe })
}) })
// DELETE /api/cpanel/databases/:id/users/:userId // DELETE /api/nixclient/databases/:id/users/:userId
fastify.delete('/:id/users/:userId', { preHandler }, async (request, reply) => { fastify.delete('/:id/users/:userId', { preHandler }, async (request, reply) => {
const { id, userId } = request.params as { id: string; userId: string } const { id, userId } = request.params as { id: string; userId: string }
@@ -8,7 +8,7 @@ import { exec } from '../../core/exec.js'
export default async function cpanelEmailRoutes(fastify: FastifyInstance) { export default async function cpanelEmailRoutes(fastify: FastifyInstance) {
const preHandler = [requireAuth] const preHandler = [requireAuth]
// GET /api/cpanel/email // GET /api/nixclient/email
fastify.get('/', { preHandler }, async (request, reply) => { fastify.get('/', { preHandler }, async (request, reply) => {
const user = request.user! const user = request.user!
const accountId = user.role === 'user' ? user.sub : parseInt((request.query as Record<string, string>).accountId ?? '0', 10) const accountId = user.role === 'user' ? user.sub : parseInt((request.query as Record<string, string>).accountId ?? '0', 10)
@@ -21,7 +21,7 @@ export default async function cpanelEmailRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: accounts }) return reply.send({ success: true, data: accounts })
}) })
// POST /api/cpanel/email // POST /api/nixclient/email
fastify.post('/', { preHandler }, async (request, reply) => { fastify.post('/', { preHandler }, async (request, reply) => {
const user = request.user! const user = request.user!
const accountId = user.role === 'user' ? user.sub : (request.body as Record<string, unknown>).accountId as number const accountId = user.role === 'user' ? user.sub : (request.body as Record<string, unknown>).accountId as number
@@ -62,7 +62,7 @@ export default async function cpanelEmailRoutes(fastify: FastifyInstance) {
return reply.code(201).send({ success: true, data: safe }) return reply.code(201).send({ success: true, data: safe })
}) })
// DELETE /api/cpanel/email/:id // DELETE /api/nixclient/email/:id
fastify.delete('/:id', { preHandler }, async (request, reply) => { fastify.delete('/:id', { preHandler }, async (request, reply) => {
const user = request.user! const user = request.user!
const accountId = user.role === 'user' ? user.sub : 0 const accountId = user.role === 'user' ? user.sub : 0
@@ -80,7 +80,7 @@ export default async function cpanelEmailRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { message: 'Email account deleted' } }) return reply.send({ success: true, data: { message: 'Email account deleted' } })
}) })
// GET /api/cpanel/email/forwarders // GET /api/nixclient/email/forwarders
fastify.get('/forwarders', { preHandler }, async (request, reply) => { fastify.get('/forwarders', { preHandler }, async (request, reply) => {
const user = request.user! const user = request.user!
const accountId = user.role === 'user' ? user.sub : parseInt((request.query as Record<string, string>).accountId ?? '0', 10) const accountId = user.role === 'user' ? user.sub : parseInt((request.query as Record<string, string>).accountId ?? '0', 10)
@@ -89,7 +89,7 @@ export default async function cpanelEmailRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: forwarders }) return reply.send({ success: true, data: forwarders })
}) })
// POST /api/cpanel/email/forwarders // POST /api/nixclient/email/forwarders
fastify.post('/forwarders', { preHandler }, async (request, reply) => { fastify.post('/forwarders', { preHandler }, async (request, reply) => {
const user = request.user! const user = request.user!
const accountId = user.role === 'user' ? user.sub : (request.body as Record<string, unknown>).accountId as number const accountId = user.role === 'user' ? user.sub : (request.body as Record<string, unknown>).accountId as number
@@ -107,7 +107,7 @@ export default async function cpanelEmailRoutes(fastify: FastifyInstance) {
return reply.code(201).send({ success: true, data: forwarder }) return reply.code(201).send({ success: true, data: forwarder })
}) })
// DELETE /api/cpanel/email/forwarders/:id // DELETE /api/nixclient/email/forwarders/:id
fastify.delete('/forwarders/:id', { preHandler }, async (request, reply) => { fastify.delete('/forwarders/:id', { preHandler }, async (request, reply) => {
const user = request.user! const user = request.user!
const accountId = user.role === 'user' ? user.sub : 0 const accountId = user.role === 'user' ? user.sub : 0
@@ -26,10 +26,10 @@ async function getAccount(fastify: FastifyInstance, userId: number) {
export default async function fileRoutes(fastify: FastifyInstance) { export default async function fileRoutes(fastify: FastifyInstance) {
const preHandler = [requireAuth] const preHandler = [requireAuth]
// GET /api/cpanel/files?path= // GET /api/nixclient/files?path=
fastify.get('/', { preHandler }, async (request, reply) => { fastify.get('/', { preHandler }, async (request, reply) => {
const user = request.user! const user = request.user!
if (user.role !== 'user') return reply.code(403).send({ success: false, error: 'cPanel users only' }) if (user.role !== 'user') return reply.code(403).send({ success: false, error: 'NixClient users only' })
const account = await getAccount(fastify, user.sub) const account = await getAccount(fastify, user.sub)
if (!account) return reply.code(404).send({ success: false, error: 'Account not found' }) if (!account) return reply.code(404).send({ success: false, error: 'Account not found' })
@@ -73,10 +73,10 @@ export default async function fileRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { path: userPath, files } }) return reply.send({ success: true, data: { path: userPath, files } })
}) })
// GET /api/cpanel/files/content?path= // GET /api/nixclient/files/content?path=
fastify.get('/content', { preHandler }, async (request, reply) => { fastify.get('/content', { preHandler }, async (request, reply) => {
const user = request.user! const user = request.user!
if (user.role !== 'user') return reply.code(403).send({ success: false, error: 'cPanel users only' }) if (user.role !== 'user') return reply.code(403).send({ success: false, error: 'NixClient users only' })
const account = await getAccount(fastify, user.sub) const account = await getAccount(fastify, user.sub)
if (!account) return reply.code(404).send({ success: false, error: 'Account not found' }) if (!account) return reply.code(404).send({ success: false, error: 'Account not found' })
@@ -102,10 +102,10 @@ export default async function fileRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { path: q.path, content } }) return reply.send({ success: true, data: { path: q.path, content } })
}) })
// PUT /api/cpanel/files/content — save file content // PUT /api/nixclient/files/content — save file content
fastify.put('/content', { preHandler }, async (request, reply) => { fastify.put('/content', { preHandler }, async (request, reply) => {
const user = request.user! const user = request.user!
if (user.role !== 'user') return reply.code(403).send({ success: false, error: 'cPanel users only' }) if (user.role !== 'user') return reply.code(403).send({ success: false, error: 'NixClient users only' })
const account = await getAccount(fastify, user.sub) const account = await getAccount(fastify, user.sub)
if (!account) return reply.code(404).send({ success: false, error: 'Account not found' }) if (!account) return reply.code(404).send({ success: false, error: 'Account not found' })
@@ -133,10 +133,10 @@ export default async function fileRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { message: 'File saved' } }) return reply.send({ success: true, data: { message: 'File saved' } })
}) })
// POST /api/cpanel/files/mkdir // POST /api/nixclient/files/mkdir
fastify.post('/mkdir', { preHandler }, async (request, reply) => { fastify.post('/mkdir', { preHandler }, async (request, reply) => {
const user = request.user! const user = request.user!
if (user.role !== 'user') return reply.code(403).send({ success: false, error: 'cPanel users only' }) if (user.role !== 'user') return reply.code(403).send({ success: false, error: 'NixClient users only' })
const account = await getAccount(fastify, user.sub) const account = await getAccount(fastify, user.sub)
if (!account) return reply.code(404).send({ success: false, error: 'Account not found' }) if (!account) return reply.code(404).send({ success: false, error: 'Account not found' })
@@ -157,10 +157,10 @@ export default async function fileRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { message: 'Directory created' } }) return reply.send({ success: true, data: { message: 'Directory created' } })
}) })
// DELETE /api/cpanel/files // DELETE /api/nixclient/files
fastify.delete('/', { preHandler }, async (request, reply) => { fastify.delete('/', { preHandler }, async (request, reply) => {
const user = request.user! const user = request.user!
if (user.role !== 'user') return reply.code(403).send({ success: false, error: 'cPanel users only' }) if (user.role !== 'user') return reply.code(403).send({ success: false, error: 'NixClient users only' })
const account = await getAccount(fastify, user.sub) const account = await getAccount(fastify, user.sub)
if (!account) return reply.code(404).send({ success: false, error: 'Account not found' }) if (!account) return reply.code(404).send({ success: false, error: 'Account not found' })
@@ -189,10 +189,10 @@ export default async function fileRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { deleted, errors } }) return reply.send({ success: true, data: { deleted, errors } })
}) })
// POST /api/cpanel/files/rename // POST /api/nixclient/files/rename
fastify.post('/rename', { preHandler }, async (request, reply) => { fastify.post('/rename', { preHandler }, async (request, reply) => {
const user = request.user! const user = request.user!
if (user.role !== 'user') return reply.code(403).send({ success: false, error: 'cPanel users only' }) if (user.role !== 'user') return reply.code(403).send({ success: false, error: 'NixClient users only' })
const account = await getAccount(fastify, user.sub) const account = await getAccount(fastify, user.sub)
if (!account) return reply.code(404).send({ success: false, error: 'Account not found' }) if (!account) return reply.code(404).send({ success: false, error: 'Account not found' })
@@ -207,10 +207,10 @@ export default async function fileRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { message: 'Renamed' } }) return reply.send({ success: true, data: { message: 'Renamed' } })
}) })
// POST /api/cpanel/files/chmod // POST /api/nixclient/files/chmod
fastify.post('/chmod', { preHandler }, async (request, reply) => { fastify.post('/chmod', { preHandler }, async (request, reply) => {
const user = request.user! const user = request.user!
if (user.role !== 'user') return reply.code(403).send({ success: false, error: 'cPanel users only' }) if (user.role !== 'user') return reply.code(403).send({ success: false, error: 'NixClient users only' })
const account = await getAccount(fastify, user.sub) const account = await getAccount(fastify, user.sub)
if (!account) return reply.code(404).send({ success: false, error: 'Account not found' }) if (!account) return reply.code(404).send({ success: false, error: 'Account not found' })
@@ -228,10 +228,10 @@ export default async function fileRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { message: 'Permissions updated' } }) return reply.send({ success: true, data: { message: 'Permissions updated' } })
}) })
// POST /api/cpanel/files/compress — zip files // POST /api/nixclient/files/compress — zip files
fastify.post('/compress', { preHandler }, async (request, reply) => { fastify.post('/compress', { preHandler }, async (request, reply) => {
const user = request.user! const user = request.user!
if (user.role !== 'user') return reply.code(403).send({ success: false, error: 'cPanel users only' }) if (user.role !== 'user') return reply.code(403).send({ success: false, error: 'NixClient users only' })
const account = await getAccount(fastify, user.sub) const account = await getAccount(fastify, user.sub)
if (!account) return reply.code(404).send({ success: false, error: 'Account not found' }) if (!account) return reply.code(404).send({ success: false, error: 'Account not found' })
@@ -7,7 +7,7 @@ import ftpRoutes from './ftp.js'
import cronRoutes from './cron.js' import cronRoutes from './cron.js'
import statsRoutes from './stats.js' import statsRoutes from './stats.js'
export default async function cpanelRoutes(fastify: FastifyInstance) { export default async function nixclientRoutes(fastify: FastifyInstance) {
fastify.register(fileRoutes, { prefix: '/files' }) fastify.register(fileRoutes, { prefix: '/files' })
fastify.register(databaseRoutes, { prefix: '/databases' }) fastify.register(databaseRoutes, { prefix: '/databases' })
fastify.register(emailRoutes, { prefix: '/email' }) fastify.register(emailRoutes, { prefix: '/email' })
@@ -6,7 +6,7 @@ import { exec } from '../../core/exec.js'
export default async function statsRoutes(fastify: FastifyInstance) { export default async function statsRoutes(fastify: FastifyInstance) {
const preHandler = [requireAuth] const preHandler = [requireAuth]
// GET /api/cpanel/stats — account resource usage summary // GET /api/nixclient/stats — account resource usage summary
fastify.get('/', { preHandler }, async (request, reply) => { fastify.get('/', { preHandler }, async (request, reply) => {
const user = request.user! const user = request.user!
const accountId = user.role === 'user' ? user.sub : parseInt((request.query as Record<string, string>).accountId ?? '0', 10) const accountId = user.role === 'user' ? user.sub : parseInt((request.query as Record<string, string>).accountId ?? '0', 10)
@@ -57,7 +57,7 @@ export default async function statsRoutes(fastify: FastifyInstance) {
}) })
}) })
// GET /api/cpanel/stats/bandwidth — bandwidth usage over time (placeholder for log parsing) // GET /api/nixclient/stats/bandwidth — bandwidth usage over time (placeholder for log parsing)
fastify.get('/bandwidth', { preHandler }, async (request, reply) => { fastify.get('/bandwidth', { preHandler }, async (request, reply) => {
const user = request.user! const user = request.user!
const accountId = user.role === 'user' ? user.sub : parseInt((request.query as Record<string, string>).accountId ?? '0', 10) const accountId = user.role === 'user' ? user.sub : parseInt((request.query as Record<string, string>).accountId ?? '0', 10)
@@ -28,7 +28,7 @@ const UpdateAccountSchema = z.object({
export default async function accountRoutes(fastify: FastifyInstance) { export default async function accountRoutes(fastify: FastifyInstance) {
const preHandler = [requireAdminOrReseller] const preHandler = [requireAdminOrReseller]
// GET /api/whm/accounts // GET /api/nixserver/accounts
fastify.get('/', { preHandler }, async (request, reply) => { fastify.get('/', { preHandler }, async (request, reply) => {
const q = request.query as Record<string, string> const q = request.query as Record<string, string>
const page = Math.max(1, parseInt(q.page ?? '1', 10)) const page = Math.max(1, parseInt(q.page ?? '1', 10))
@@ -69,7 +69,7 @@ export default async function accountRoutes(fastify: FastifyInstance) {
}) })
}) })
// GET /api/whm/accounts/stats // GET /api/nixserver/accounts/stats
fastify.get('/stats', { preHandler }, async (request, reply) => { fastify.get('/stats', { preHandler }, async (request, reply) => {
const where = request.user!.role === 'reseller' ? { resellerId: request.user!.sub } : {} const where = request.user!.role === 'reseller' ? { resellerId: request.user!.sub } : {}
@@ -83,7 +83,7 @@ export default async function accountRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { total, active, suspended, terminated } }) return reply.send({ success: true, data: { total, active, suspended, terminated } })
}) })
// GET /api/whm/accounts/:id // GET /api/nixserver/accounts/:id
fastify.get('/:id', { preHandler }, async (request, reply) => { fastify.get('/:id', { preHandler }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
const where: Record<string, unknown> = { id: parseInt(id, 10) } const where: Record<string, unknown> = { id: parseInt(id, 10) }
@@ -111,7 +111,7 @@ export default async function accountRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: safe }) return reply.send({ success: true, data: safe })
}) })
// POST /api/whm/accounts // POST /api/nixserver/accounts
fastify.post('/', { preHandler }, async (request, reply) => { fastify.post('/', { preHandler }, async (request, reply) => {
const body = CreateAccountSchema.safeParse(request.body) const body = CreateAccountSchema.safeParse(request.body)
if (!body.success) { if (!body.success) {
@@ -228,7 +228,7 @@ export default async function accountRoutes(fastify: FastifyInstance) {
return reply.code(201).send({ success: true, data: safe }) return reply.code(201).send({ success: true, data: safe })
}) })
// PUT /api/whm/accounts/:id // PUT /api/nixserver/accounts/:id
fastify.put('/:id', { preHandler }, async (request, reply) => { fastify.put('/:id', { preHandler }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
const body = UpdateAccountSchema.safeParse(request.body) const body = UpdateAccountSchema.safeParse(request.body)
@@ -261,7 +261,7 @@ export default async function accountRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: safe }) return reply.send({ success: true, data: safe })
}) })
// POST /api/whm/accounts/:id/suspend // POST /api/nixserver/accounts/:id/suspend
fastify.post('/:id/suspend', { preHandler }, async (request, reply) => { fastify.post('/:id/suspend', { preHandler }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
const { reason } = (request.body as Record<string, string>) ?? {} const { reason } = (request.body as Record<string, string>) ?? {}
@@ -291,7 +291,7 @@ export default async function accountRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { message: `Account ${account.username} suspended` } }) return reply.send({ success: true, data: { message: `Account ${account.username} suspended` } })
}) })
// POST /api/whm/accounts/:id/unsuspend // POST /api/nixserver/accounts/:id/unsuspend
fastify.post('/:id/unsuspend', { preHandler }, async (request, reply) => { fastify.post('/:id/unsuspend', { preHandler }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
@@ -319,7 +319,7 @@ export default async function accountRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { message: `Account ${account.username} unsuspended` } }) return reply.send({ success: true, data: { message: `Account ${account.username} unsuspended` } })
}) })
// DELETE /api/whm/accounts/:id // DELETE /api/nixserver/accounts/:id
fastify.delete('/:id', { preHandler }, async (request, reply) => { fastify.delete('/:id', { preHandler }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
const { removeFiles } = (request.body as Record<string, boolean>) ?? {} const { removeFiles } = (request.body as Record<string, boolean>) ?? {}
@@ -364,7 +364,7 @@ export default async function accountRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { message: `Account ${account.username} terminated` } }) return reply.send({ success: true, data: { message: `Account ${account.username} terminated` } })
}) })
// POST /api/whm/accounts/bulk // POST /api/nixserver/accounts/bulk
fastify.post('/bulk', { preHandler }, async (request, reply) => { fastify.post('/bulk', { preHandler }, async (request, reply) => {
const body = z.object({ const body = z.object({
action: z.enum(['suspend', 'unsuspend', 'terminate']), action: z.enum(['suspend', 'unsuspend', 'terminate']),
@@ -9,7 +9,7 @@ import { config } from '../../config.js'
const BACKUP_DIR = '/var/nixpanel/backups' const BACKUP_DIR = '/var/nixpanel/backups'
export default async function backupRoutes(fastify: FastifyInstance) { export default async function backupRoutes(fastify: FastifyInstance) {
// POST /api/whm/backup/account/:id — backup a single account // POST /api/nixserver/backup/account/:id — backup a single account
fastify.post('/account/:id', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.post('/account/:id', { preHandler: [requireAdmin] }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
const account = await prisma.account.findUnique({ where: { id: parseInt(id, 10) } }) const account = await prisma.account.findUnique({ where: { id: parseInt(id, 10) } })
@@ -39,7 +39,7 @@ export default async function backupRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { filename, path: dest, size, createdAt: new Date() } }) return reply.send({ success: true, data: { filename, path: dest, size, createdAt: new Date() } })
}) })
// POST /api/whm/backup/server — full server backup // POST /api/nixserver/backup/server — full server backup
fastify.post('/server', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.post('/server', { preHandler: [requireAdmin] }, async (request, reply) => {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-') const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
const filename = `server-backup-${timestamp}.tar.gz` const filename = `server-backup-${timestamp}.tar.gz`
@@ -61,7 +61,7 @@ export default async function backupRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { filename, path: dest, createdAt: new Date() } }) return reply.send({ success: true, data: { filename, path: dest, createdAt: new Date() } })
}) })
// GET /api/whm/backup/list — list available backups // GET /api/nixserver/backup/list — list available backups
fastify.get('/list', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/list', { preHandler: [requireAdmin] }, async (request, reply) => {
const result = await exec('find', [BACKUP_DIR, '-name', '*.tar.gz', '-printf', '%p %s %T@\n']) const result = await exec('find', [BACKUP_DIR, '-name', '*.tar.gz', '-printf', '%p %s %T@\n'])
if (result.exitCode !== 0 && !result.stdout) { if (result.exitCode !== 0 && !result.stdout) {
@@ -81,7 +81,7 @@ export default async function backupRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: backups }) return reply.send({ success: true, data: backups })
}) })
// DELETE /api/whm/backup — delete a backup file // DELETE /api/nixserver/backup — delete a backup file
fastify.delete('/', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.delete('/', { preHandler: [requireAdmin] }, async (request, reply) => {
const body = z.object({ path: z.string() }).safeParse(request.body) const body = z.object({ path: z.string() }).safeParse(request.body)
if (!body.success) { if (!body.success) {
@@ -96,7 +96,7 @@ export default async function backupRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { message: 'Backup deleted' } }) return reply.send({ success: true, data: { message: 'Backup deleted' } })
}) })
// POST /api/whm/backup/restore/:filename // POST /api/nixserver/backup/restore/:filename
fastify.post('/restore/:filename', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.post('/restore/:filename', { preHandler: [requireAdmin] }, async (request, reply) => {
const { filename } = request.params as { filename: string } const { filename } = request.params as { filename: string }
// Sanitize — no path traversal // Sanitize — no path traversal
@@ -16,7 +16,7 @@ const DnsRecordSchema = z.object({
export default async function dnsRoutes(fastify: FastifyInstance) { export default async function dnsRoutes(fastify: FastifyInstance) {
const preHandler = [requireAdminOrReseller] const preHandler = [requireAdminOrReseller]
// GET /api/whm/dns // GET /api/nixserver/dns
fastify.get('/', { preHandler }, async (request, reply) => { fastify.get('/', { preHandler }, async (request, reply) => {
const q = request.query as Record<string, string> const q = request.query as Record<string, string>
const where: Record<string, unknown> = {} const where: Record<string, unknown> = {}
@@ -34,7 +34,7 @@ export default async function dnsRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: zones }) return reply.send({ success: true, data: zones })
}) })
// GET /api/whm/dns/:id // GET /api/nixserver/dns/:id
fastify.get('/:id', { preHandler }, async (request, reply) => { fastify.get('/:id', { preHandler }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
const zone = await prisma.dnsZone.findUnique({ const zone = await prisma.dnsZone.findUnique({
@@ -48,7 +48,7 @@ export default async function dnsRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: zone }) return reply.send({ success: true, data: zone })
}) })
// POST /api/whm/dns/:id/records // POST /api/nixserver/dns/:id/records
fastify.post('/:id/records', { preHandler }, async (request, reply) => { fastify.post('/:id/records', { preHandler }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
const body = DnsRecordSchema.safeParse(request.body) const body = DnsRecordSchema.safeParse(request.body)
@@ -72,7 +72,7 @@ export default async function dnsRoutes(fastify: FastifyInstance) {
return reply.code(201).send({ success: true, data: record }) return reply.code(201).send({ success: true, data: record })
}) })
// PUT /api/whm/dns/:zoneId/records/:recordId // PUT /api/nixserver/dns/:zoneId/records/:recordId
fastify.put('/:zoneId/records/:recordId', { preHandler }, async (request, reply) => { fastify.put('/:zoneId/records/:recordId', { preHandler }, async (request, reply) => {
const { zoneId, recordId } = request.params as { zoneId: string; recordId: string } const { zoneId, recordId } = request.params as { zoneId: string; recordId: string }
const body = DnsRecordSchema.partial().safeParse(request.body) const body = DnsRecordSchema.partial().safeParse(request.body)
@@ -95,7 +95,7 @@ export default async function dnsRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: updated }) return reply.send({ success: true, data: updated })
}) })
// DELETE /api/whm/dns/:zoneId/records/:recordId // DELETE /api/nixserver/dns/:zoneId/records/:recordId
fastify.delete('/:zoneId/records/:recordId', { preHandler }, async (request, reply) => { fastify.delete('/:zoneId/records/:recordId', { preHandler }, async (request, reply) => {
const { zoneId, recordId } = request.params as { zoneId: string; recordId: string } const { zoneId, recordId } = request.params as { zoneId: string; recordId: string }
@@ -110,7 +110,7 @@ export default async function dnsRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { message: 'Record deleted' } }) return reply.send({ success: true, data: { message: 'Record deleted' } })
}) })
// POST /api/whm/dns/:id/reload // POST /api/nixserver/dns/:id/reload
fastify.post('/:id/reload', { preHandler }, async (request, reply) => { fastify.post('/:id/reload', { preHandler }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
await rebuildZone(parseInt(id, 10)) await rebuildZone(parseInt(id, 10))
@@ -8,7 +8,7 @@ import { exec } from '../../core/exec.js'
export default async function emailRoutes(fastify: FastifyInstance) { export default async function emailRoutes(fastify: FastifyInstance) {
const preHandler = [requireAdminOrReseller] const preHandler = [requireAdminOrReseller]
// GET /api/whm/email?accountId= // GET /api/nixserver/email?accountId=
fastify.get('/', { preHandler }, async (request, reply) => { fastify.get('/', { preHandler }, async (request, reply) => {
const q = request.query as Record<string, string> const q = request.query as Record<string, string>
const where: Record<string, unknown> = {} const where: Record<string, unknown> = {}
@@ -27,7 +27,7 @@ export default async function emailRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: accounts }) return reply.send({ success: true, data: accounts })
}) })
// POST /api/whm/email // POST /api/nixserver/email
fastify.post('/', { preHandler }, async (request, reply) => { fastify.post('/', { preHandler }, async (request, reply) => {
const body = z.object({ const body = z.object({
accountId: z.number().int().positive(), accountId: z.number().int().positive(),
@@ -81,7 +81,7 @@ export default async function emailRoutes(fastify: FastifyInstance) {
return reply.code(201).send({ success: true, data: safe }) return reply.code(201).send({ success: true, data: safe })
}) })
// DELETE /api/whm/email/:id // DELETE /api/nixserver/email/:id
fastify.delete('/:id', { preHandler }, async (request, reply) => { fastify.delete('/:id', { preHandler }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
const emailAccount = await prisma.emailAccount.findUnique({ where: { id: parseInt(id, 10) } }) const emailAccount = await prisma.emailAccount.findUnique({ where: { id: parseInt(id, 10) } })
@@ -93,7 +93,7 @@ export default async function emailRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { message: 'Email account deleted' } }) return reply.send({ success: true, data: { message: 'Email account deleted' } })
}) })
// POST /api/whm/email/:id/password // POST /api/nixserver/email/:id/password
fastify.post('/:id/password', { preHandler }, async (request, reply) => { fastify.post('/:id/password', { preHandler }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
const body = z.object({ password: z.string().min(8) }).safeParse(request.body) const body = z.object({ password: z.string().min(8) }).safeParse(request.body)
@@ -110,7 +110,7 @@ export default async function emailRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { message: 'Password updated' } }) return reply.send({ success: true, data: { message: 'Password updated' } })
}) })
// GET /api/whm/email/forwarders?accountId= // GET /api/nixserver/email/forwarders?accountId=
fastify.get('/forwarders', { preHandler }, async (request, reply) => { fastify.get('/forwarders', { preHandler }, async (request, reply) => {
const q = request.query as Record<string, string> const q = request.query as Record<string, string>
const where: Record<string, unknown> = {} const where: Record<string, unknown> = {}
@@ -120,7 +120,7 @@ export default async function emailRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: forwarders }) return reply.send({ success: true, data: forwarders })
}) })
// POST /api/whm/email/forwarders // POST /api/nixserver/email/forwarders
fastify.post('/forwarders', { preHandler }, async (request, reply) => { fastify.post('/forwarders', { preHandler }, async (request, reply) => {
const body = z.object({ const body = z.object({
accountId: z.number().int().positive(), accountId: z.number().int().positive(),
@@ -136,7 +136,7 @@ export default async function emailRoutes(fastify: FastifyInstance) {
return reply.code(201).send({ success: true, data: forwarder }) return reply.code(201).send({ success: true, data: forwarder })
}) })
// DELETE /api/whm/email/forwarders/:id // DELETE /api/nixserver/email/forwarders/:id
fastify.delete('/forwarders/:id', { preHandler }, async (request, reply) => { fastify.delete('/forwarders/:id', { preHandler }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
await prisma.emailForwarder.delete({ where: { id: parseInt(id, 10) } }) await prisma.emailForwarder.delete({ where: { id: parseInt(id, 10) } })
@@ -12,7 +12,7 @@ import securityRoutes from './security.js'
import backupRoutes from './backup.js' import backupRoutes from './backup.js'
import webserverRoutes from './webserver.js' import webserverRoutes from './webserver.js'
export default async function whmRoutes(fastify: FastifyInstance) { export default async function nixserverRoutes(fastify: FastifyInstance) {
fastify.register(accountRoutes, { prefix: '/accounts' }) fastify.register(accountRoutes, { prefix: '/accounts' })
fastify.register(packageRoutes, { prefix: '/packages' }) fastify.register(packageRoutes, { prefix: '/packages' })
fastify.register(resellerRoutes, { prefix: '/resellers' }) fastify.register(resellerRoutes, { prefix: '/resellers' })
@@ -8,7 +8,7 @@ import { writeFile } from '../../core/exec.js'
const SUPPORTED_PHP_VERSIONS = ['7.4', '8.0', '8.1', '8.2', '8.3'] const SUPPORTED_PHP_VERSIONS = ['7.4', '8.0', '8.1', '8.2', '8.3']
export default async function phpRoutes(fastify: FastifyInstance) { export default async function phpRoutes(fastify: FastifyInstance) {
// GET /api/whm/php/versions — list installed PHP versions // GET /api/nixserver/php/versions — list installed PHP versions
fastify.get('/versions', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/versions', { preHandler: [requireAdmin] }, async (request, reply) => {
const versions: { version: string; installed: boolean; active: boolean }[] = [] const versions: { version: string; installed: boolean; active: boolean }[] = []
@@ -21,7 +21,7 @@ export default async function phpRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: versions }) return reply.send({ success: true, data: versions })
}) })
// GET /api/whm/php/accounts — list which PHP version each account uses // GET /api/nixserver/php/accounts — list which PHP version each account uses
fastify.get('/accounts', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/accounts', { preHandler: [requireAdmin] }, async (request, reply) => {
const accounts = await prisma.account.findMany({ const accounts = await prisma.account.findMany({
where: { status: 'active' }, where: { status: 'active' },
@@ -33,7 +33,7 @@ export default async function phpRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: accounts }) return reply.send({ success: true, data: accounts })
}) })
// PUT /api/whm/php/accounts/:id — change PHP version for account // PUT /api/nixserver/php/accounts/:id — change PHP version for account
fastify.put('/accounts/:id', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.put('/accounts/:id', { preHandler: [requireAdmin] }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
const body = z.object({ const body = z.object({
@@ -82,7 +82,7 @@ php_admin_value[error_log] = ${account.homedir}/logs/php_errors.log
return reply.send({ success: true, data: { message: `PHP version set to ${body.data.phpVersion}` } }) return reply.send({ success: true, data: { message: `PHP version set to ${body.data.phpVersion}` } })
}) })
// GET /api/whm/php/ini/:version — get php.ini settings // GET /api/nixserver/php/ini/:version — get php.ini settings
fastify.get('/ini/:version', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/ini/:version', { preHandler: [requireAdmin] }, async (request, reply) => {
const { version } = request.params as { version: string } const { version } = request.params as { version: string }
if (!SUPPORTED_PHP_VERSIONS.includes(version)) { if (!SUPPORTED_PHP_VERSIONS.includes(version)) {
@@ -5,7 +5,7 @@ import { requireAdmin } from '../../middleware/auth.js'
import { exec } from '../../core/exec.js' import { exec } from '../../core/exec.js'
export default async function securityRoutes(fastify: FastifyInstance) { export default async function securityRoutes(fastify: FastifyInstance) {
// GET /api/whm/security/events // GET /api/nixserver/security/events
fastify.get('/events', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/events', { preHandler: [requireAdmin] }, async (request, reply) => {
const q = request.query as Record<string, string> const q = request.query as Record<string, string>
const page = Math.max(1, parseInt(q.page ?? '1', 10)) const page = Math.max(1, parseInt(q.page ?? '1', 10))
@@ -25,7 +25,7 @@ export default async function securityRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { items, total, page, limit, totalPages: Math.ceil(total / limit) } }) return reply.send({ success: true, data: { items, total, page, limit, totalPages: Math.ceil(total / limit) } })
}) })
// POST /api/whm/security/events/:id/resolve // POST /api/nixserver/security/events/:id/resolve
fastify.post('/events/:id/resolve', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.post('/events/:id/resolve', { preHandler: [requireAdmin] }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
await prisma.securityEvent.update({ await prisma.securityEvent.update({
@@ -35,13 +35,13 @@ export default async function securityRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { message: 'Event resolved' } }) return reply.send({ success: true, data: { message: 'Event resolved' } })
}) })
// GET /api/whm/security/blocked-ips // GET /api/nixserver/security/blocked-ips
fastify.get('/blocked-ips', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/blocked-ips', { preHandler: [requireAdmin] }, async (request, reply) => {
const blocked = await prisma.blockedIp.findMany({ orderBy: { createdAt: 'desc' } }) const blocked = await prisma.blockedIp.findMany({ orderBy: { createdAt: 'desc' } })
return reply.send({ success: true, data: blocked }) return reply.send({ success: true, data: blocked })
}) })
// POST /api/whm/security/blocked-ips // POST /api/nixserver/security/blocked-ips
fastify.post('/blocked-ips', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.post('/blocked-ips', { preHandler: [requireAdmin] }, async (request, reply) => {
const body = z.object({ const body = z.object({
ip: z.string().ip(), ip: z.string().ip(),
@@ -69,7 +69,7 @@ export default async function securityRoutes(fastify: FastifyInstance) {
return reply.code(201).send({ success: true, data: blocked }) return reply.code(201).send({ success: true, data: blocked })
}) })
// DELETE /api/whm/security/blocked-ips/:id // DELETE /api/nixserver/security/blocked-ips/:id
fastify.delete('/blocked-ips/:id', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.delete('/blocked-ips/:id', { preHandler: [requireAdmin] }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
const blocked = await prisma.blockedIp.findUnique({ where: { id: parseInt(id, 10) } }) const blocked = await prisma.blockedIp.findUnique({ where: { id: parseInt(id, 10) } })
@@ -81,13 +81,13 @@ export default async function securityRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { message: `${blocked.ip} unblocked` } }) return reply.send({ success: true, data: { message: `${blocked.ip} unblocked` } })
}) })
// GET /api/whm/security/whitelisted-ips // GET /api/nixserver/security/whitelisted-ips
fastify.get('/whitelisted-ips', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/whitelisted-ips', { preHandler: [requireAdmin] }, async (request, reply) => {
const list = await prisma.whitelistedIp.findMany({ orderBy: { createdAt: 'desc' } }) const list = await prisma.whitelistedIp.findMany({ orderBy: { createdAt: 'desc' } })
return reply.send({ success: true, data: list }) return reply.send({ success: true, data: list })
}) })
// POST /api/whm/security/whitelisted-ips // POST /api/nixserver/security/whitelisted-ips
fastify.post('/whitelisted-ips', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.post('/whitelisted-ips', { preHandler: [requireAdmin] }, async (request, reply) => {
const body = z.object({ const body = z.object({
ip: z.string().ip(), ip: z.string().ip(),
@@ -102,20 +102,20 @@ export default async function securityRoutes(fastify: FastifyInstance) {
return reply.code(201).send({ success: true, data: entry }) return reply.code(201).send({ success: true, data: entry })
}) })
// DELETE /api/whm/security/whitelisted-ips/:id // DELETE /api/nixserver/security/whitelisted-ips/:id
fastify.delete('/whitelisted-ips/:id', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.delete('/whitelisted-ips/:id', { preHandler: [requireAdmin] }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
await prisma.whitelistedIp.delete({ where: { id: parseInt(id, 10) } }) await prisma.whitelistedIp.delete({ where: { id: parseInt(id, 10) } })
return reply.send({ success: true, data: { message: 'Removed from whitelist' } }) return reply.send({ success: true, data: { message: 'Removed from whitelist' } })
}) })
// GET /api/whm/security/fail2ban // GET /api/nixserver/security/fail2ban
fastify.get('/fail2ban', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/fail2ban', { preHandler: [requireAdmin] }, async (request, reply) => {
const result = await exec('fail2ban-client', ['status']) const result = await exec('fail2ban-client', ['status'])
return reply.send({ success: true, data: { output: result.stdout } }) return reply.send({ success: true, data: { output: result.stdout } })
}) })
// GET /api/whm/security/fail2ban/:jail // GET /api/nixserver/security/fail2ban/:jail
fastify.get('/fail2ban/:jail', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/fail2ban/:jail', { preHandler: [requireAdmin] }, async (request, reply) => {
const { jail } = request.params as { jail: string } const { jail } = request.params as { jail: string }
const result = await exec('fail2ban-client', ['status', jail]) const result = await exec('fail2ban-client', ['status', jail])
@@ -9,13 +9,13 @@ const ServiceActionSchema = z.object({
}) })
export default async function serviceRoutes(fastify: FastifyInstance) { export default async function serviceRoutes(fastify: FastifyInstance) {
// GET /api/whm/services — list all managed services and their status // GET /api/nixserver/services — list all managed services and their status
fastify.get('/', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/', { preHandler: [requireAdmin] }, async (request, reply) => {
const statuses = await Promise.all(MANAGED_SERVICES.map(getServiceStatus)) const statuses = await Promise.all(MANAGED_SERVICES.map(getServiceStatus))
return reply.send({ success: true, data: statuses }) return reply.send({ success: true, data: statuses })
}) })
// GET /api/whm/services/:name // GET /api/nixserver/services/:name
fastify.get('/:name', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/:name', { preHandler: [requireAdmin] }, async (request, reply) => {
const { name } = request.params as { name: string } const { name } = request.params as { name: string }
if (!MANAGED_SERVICES.includes(name)) { if (!MANAGED_SERVICES.includes(name)) {
@@ -25,7 +25,7 @@ export default async function serviceRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: status }) return reply.send({ success: true, data: status })
}) })
// POST /api/whm/services/:name — start/stop/restart/reload/enable/disable // POST /api/nixserver/services/:name — start/stop/restart/reload/enable/disable
fastify.post('/:name', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.post('/:name', { preHandler: [requireAdmin] }, async (request, reply) => {
const { name } = request.params as { name: string } const { name } = request.params as { name: string }
const body = ServiceActionSchema.safeParse(request.body) const body = ServiceActionSchema.safeParse(request.body)
@@ -10,7 +10,7 @@ import { config } from '../../config.js'
export default async function sslRoutes(fastify: FastifyInstance) { export default async function sslRoutes(fastify: FastifyInstance) {
const preHandler = [requireAdminOrReseller] const preHandler = [requireAdminOrReseller]
// GET /api/whm/ssl // GET /api/nixserver/ssl
fastify.get('/', { preHandler }, async (request, reply) => { fastify.get('/', { preHandler }, async (request, reply) => {
const q = request.query as Record<string, string> const q = request.query as Record<string, string>
const where: Record<string, unknown> = {} const where: Record<string, unknown> = {}
@@ -25,7 +25,7 @@ export default async function sslRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: certs }) return reply.send({ success: true, data: certs })
}) })
// POST /api/whm/ssl/issue — issue Let's Encrypt cert via certbot // POST /api/nixserver/ssl/issue — issue Let's Encrypt cert via certbot
fastify.post('/issue', { preHandler }, async (request, reply) => { fastify.post('/issue', { preHandler }, async (request, reply) => {
const body = z.object({ const body = z.object({
accountId: z.number().int().positive(), accountId: z.number().int().positive(),
@@ -100,7 +100,7 @@ export default async function sslRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: cert }) return reply.send({ success: true, data: cert })
}) })
// POST /api/whm/ssl/revoke // POST /api/nixserver/ssl/revoke
fastify.post('/:id/revoke', { preHandler }, async (request, reply) => { fastify.post('/:id/revoke', { preHandler }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
const cert = await prisma.sslCertificate.findUnique({ where: { id: parseInt(id, 10) } }) const cert = await prisma.sslCertificate.findUnique({ where: { id: parseInt(id, 10) } })
@@ -121,7 +121,7 @@ export default async function sslRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { message: 'Certificate revoked' } }) return reply.send({ success: true, data: { message: 'Certificate revoked' } })
}) })
// POST /api/whm/ssl/:id/renew // POST /api/nixserver/ssl/:id/renew
fastify.post('/:id/renew', { preHandler }, async (request, reply) => { fastify.post('/:id/renew', { preHandler }, async (request, reply) => {
const { id } = request.params as { id: string } const { id } = request.params as { id: string }
const cert = await prisma.sslCertificate.findUnique({ where: { id: parseInt(id, 10) } }) const cert = await prisma.sslCertificate.findUnique({ where: { id: parseInt(id, 10) } })
@@ -6,13 +6,13 @@ import { exec } from '../../core/exec.js'
import { readFile } from 'fs/promises' import { readFile } from 'fs/promises'
export default async function systemRoutes(fastify: FastifyInstance) { export default async function systemRoutes(fastify: FastifyInstance) {
// GET /api/whm/system/stats // GET /api/nixserver/system/stats
fastify.get('/stats', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/stats', { preHandler: [requireAdmin] }, async (request, reply) => {
const stats = await getSystemStats() const stats = await getSystemStats()
return reply.send({ success: true, data: stats }) return reply.send({ success: true, data: stats })
}) })
// GET /api/whm/system/info // GET /api/nixserver/system/info
fastify.get('/info', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/info', { preHandler: [requireAdmin] }, async (request, reply) => {
const [hostname, osRelease, kernel, uptime] = await Promise.all([ const [hostname, osRelease, kernel, uptime] = await Promise.all([
exec('hostname', ['-f']), exec('hostname', ['-f']),
@@ -39,7 +39,7 @@ export default async function systemRoutes(fastify: FastifyInstance) {
}) })
}) })
// GET /api/whm/system/processes // GET /api/nixserver/system/processes
fastify.get('/processes', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/processes', { preHandler: [requireAdmin] }, async (request, reply) => {
const result = await exec('ps', ['aux', '--sort=-%cpu']) const result = await exec('ps', ['aux', '--sort=-%cpu'])
if (result.exitCode !== 0) { if (result.exitCode !== 0) {
@@ -61,7 +61,7 @@ export default async function systemRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: processes }) return reply.send({ success: true, data: processes })
}) })
// POST /api/whm/system/processes/:pid/kill // POST /api/nixserver/system/processes/:pid/kill
fastify.post('/processes/:pid/kill', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.post('/processes/:pid/kill', { preHandler: [requireAdmin] }, async (request, reply) => {
const { pid } = request.params as { pid: string } const { pid } = request.params as { pid: string }
if (!/^\d+$/.test(pid) || parseInt(pid, 10) <= 1) { if (!/^\d+$/.test(pid) || parseInt(pid, 10) <= 1) {
@@ -74,7 +74,7 @@ export default async function systemRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: { message: `Process ${pid} killed` } }) return reply.send({ success: true, data: { message: `Process ${pid} killed` } })
}) })
// GET /api/whm/system/audit-log // GET /api/nixserver/system/audit-log
fastify.get('/audit-log', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/audit-log', { preHandler: [requireAdmin] }, async (request, reply) => {
const q = request.query as Record<string, string> const q = request.query as Record<string, string>
const page = Math.max(1, parseInt(q.page ?? '1', 10)) const page = Math.max(1, parseInt(q.page ?? '1', 10))
@@ -96,7 +96,7 @@ export default async function systemRoutes(fastify: FastifyInstance) {
}) })
}) })
// GET /api/whm/system/settings // GET /api/nixserver/system/settings
fastify.get('/settings', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/settings', { preHandler: [requireAdmin] }, async (request, reply) => {
const settings = await prisma.setting.findMany({ orderBy: { key: 'asc' } }) const settings = await prisma.setting.findMany({ orderBy: { key: 'asc' } })
const map: Record<string, string> = {} const map: Record<string, string> = {}
@@ -104,7 +104,7 @@ export default async function systemRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: map }) return reply.send({ success: true, data: map })
}) })
// PUT /api/whm/system/settings // PUT /api/nixserver/system/settings
fastify.put('/settings', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.put('/settings', { preHandler: [requireAdmin] }, async (request, reply) => {
const body = request.body as Record<string, string> const body = request.body as Record<string, string>
if (typeof body !== 'object' || Array.isArray(body)) { if (typeof body !== 'object' || Array.isArray(body)) {
@@ -5,7 +5,7 @@ import { exec } from '../../core/exec.js'
import { readFile } from 'fs/promises' import { readFile } from 'fs/promises'
export default async function webserverRoutes(fastify: FastifyInstance) { export default async function webserverRoutes(fastify: FastifyInstance) {
// GET /api/whm/webserver/status // GET /api/nixserver/webserver/status
fastify.get('/status', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/status', { preHandler: [requireAdmin] }, async (request, reply) => {
const [nginx, apache] = await Promise.all([ const [nginx, apache] = await Promise.all([
exec('systemctl', ['is-active', 'nginx']), exec('systemctl', ['is-active', 'nginx']),
@@ -20,7 +20,7 @@ export default async function webserverRoutes(fastify: FastifyInstance) {
}) })
}) })
// GET /api/whm/webserver/vhosts // GET /api/nixserver/webserver/vhosts
fastify.get('/vhosts', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/vhosts', { preHandler: [requireAdmin] }, async (request, reply) => {
const result = await exec('find', ['/etc/nginx/sites-enabled', '-name', '*.conf', '-type', 'f']) const result = await exec('find', ['/etc/nginx/sites-enabled', '-name', '*.conf', '-type', 'f'])
const files = result.stdout.split('\n').filter(Boolean) const files = result.stdout.split('\n').filter(Boolean)
@@ -31,7 +31,7 @@ export default async function webserverRoutes(fastify: FastifyInstance) {
return reply.send({ success: true, data: vhosts }) return reply.send({ success: true, data: vhosts })
}) })
// GET /api/whm/webserver/vhosts/:domain // GET /api/nixserver/webserver/vhosts/:domain
fastify.get('/vhosts/:domain', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/vhosts/:domain', { preHandler: [requireAdmin] }, async (request, reply) => {
const { domain } = request.params as { domain: string } const { domain } = request.params as { domain: string }
if (domain.includes('/') || domain.includes('..')) { if (domain.includes('/') || domain.includes('..')) {
@@ -45,7 +45,7 @@ export default async function webserverRoutes(fastify: FastifyInstance) {
} }
}) })
// POST /api/whm/webserver/nginx/test // POST /api/nixserver/webserver/nginx/test
fastify.post('/nginx/test', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.post('/nginx/test', { preHandler: [requireAdmin] }, async (request, reply) => {
const result = await exec('nginx', ['-t']) const result = await exec('nginx', ['-t'])
return reply.send({ return reply.send({
@@ -54,7 +54,7 @@ export default async function webserverRoutes(fastify: FastifyInstance) {
}) })
}) })
// POST /api/whm/webserver/nginx/reload // POST /api/nixserver/webserver/nginx/reload
fastify.post('/nginx/reload', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.post('/nginx/reload', { preHandler: [requireAdmin] }, async (request, reply) => {
const test = await exec('nginx', ['-t']) const test = await exec('nginx', ['-t'])
if (test.exitCode !== 0) { if (test.exitCode !== 0) {
@@ -64,7 +64,7 @@ export default async function webserverRoutes(fastify: FastifyInstance) {
return reply.send({ success: result.exitCode === 0, data: { output: result.stdout } }) return reply.send({ success: result.exitCode === 0, data: { output: result.stdout } })
}) })
// GET /api/whm/webserver/logs/:type — access or error log (last 200 lines) // GET /api/nixserver/webserver/logs/:type — access or error log (last 200 lines)
fastify.get('/logs/:type', { preHandler: [requireAdmin] }, async (request, reply) => { fastify.get('/logs/:type', { preHandler: [requireAdmin] }, async (request, reply) => {
const { type } = request.params as { type: string } const { type } = request.params as { type: string }
const logFile = type === 'error' ? '/var/log/nginx/error.log' : '/var/log/nginx/access.log' const logFile = type === 'error' ? '/var/log/nginx/error.log' : '/var/log/nginx/access.log'