11 Commits
Author SHA1 Message Date
NixReaper 3902935f60 fix(install): read from /dev/tty so curl | bash confirmation prompt works, bump v0.5.2
set -euo pipefail causes the script to exit when read gets EOF from
the curl pipe instead of an interactive terminal. Fix by appending
'< /dev/tty || CONFIRM=Y' so the prompt reads from the actual terminal
regardless of how the script was invoked.
2026-04-12 04:39:14 -05:00
NixReaper da55367039 fix(install): remove --silent from build steps, clean node_modules before install, bump v0.5.1 2026-04-11 16:15:53 -05:00
NixReaper 55786bfa0e fix: never commit package-lock.json, clean install in update.sh, bump v0.5.0
package-lock.json generated on Windows includes win32 rollup binary but
not linux-x64-gnu, breaking the frontend build on the server.

- Add package-lock.json to .gitignore and remove it from git tracking
- update.sh: rm -rf node_modules + package-lock.json before npm install
  so the server always generates a platform-correct lock file from scratch
- update.sh: remove --silent from build steps so errors are visible
- .gitignore: fix stale whm/cpanel dist paths → nixserver/nixclient
2026-04-11 15:32:29 -05:00
NixReaper 70908745bd fix(update): discard local package-lock.json before git pull, bump v0.4.9
npm install modifies package-lock.json on the server, causing
'git pull --ff-only' to abort with 'your local changes would be
overwritten'. Add 'git checkout -- package-lock.json' before the pull
so the generated lock file never blocks updates.
2026-04-11 15:03:46 -05:00
NixReaper 0362810681 fix(login): fetch version from /api/health instead of hardcoding, bump v0.4.8
Login page had 'NixServer v0.3.2 · Port 2087' hardcoded and subtitle
said 'Web Host Manager'. Now fetches version from GET /api/health on
mount (no auth needed) and shows it dynamically. Changed subtitle to
'Admin Panel'.
2026-04-11 15:02:18 -05:00
NixReaper 9ec39c9120 fix: regenerate package-lock.json after @nixpanel/whm → @nixpanel/nixserver rename, bump v0.4.7
The old package-lock.json still referenced @nixpanel/whm as a workspace.
After the package rename in v0.4.6, npm install failed to resolve the
workspace correctly, causing the nixserver frontend build to fail and
breaking login. Deleted and regenerated package-lock.json cleanly.
2026-04-11 15:01:46 -05:00
NixReaper b7eb76d91a chore: remove WHM trademark references, rename package, bump v0.4.6
WHM is a registered trademark of cPanel, L.L.C. Replace all occurrences:
- Sidebar: 'Basic WebHost Manager® Setup' → 'Basic NixServer Setup'
- Sidebar: 'WHM Marketplace' → 'NixServer Marketplace'
- Sidebar: 'OpenAPI Documentation for WHM' → 'OpenAPI Documentation for NixServer'
- nixserver/package.json: @nixpanel/whm → @nixpanel/nixserver
- server/src/app.ts: whmDist variable → nixserverDist
2026-04-11 14:45:50 -05:00
NixReaper 5af2762634 fix(version): cache-bust refresh + clear stale state on manual check, bump to v0.4.5
The refresh/retry button called fetchVersion() without clearing
versionInfo first, so the old value stayed displayed with no feedback.
On manual refresh it now immediately sets versionInfo to null (shows
spinner) then fetches with ?_t=Date.now() to bypass any browser cache
of the API response.

Bump version to 0.4.5 to include all fixes since 0.4.4 (day/night
toggle, uptime/kill allowlist, auto-retry on version error).
2026-04-11 14:27:19 -05:00
NixReaper ddb51a8b4d feat: day/night mode toggle with theme context
Add ThemeContext (dark/light, persisted to localStorage, applied as
data-theme on <html>). Theme toggle is a sun/moon flip switch at the
right end of the dashboard info bar.

CSS overrides in index.css target every hardcoded Tailwind arbitrary
colour class used across all pages via [data-theme="light"] selectors,
so the entire panel switches without touching individual page files.

Light palette: page #f0f4f8, cards/sidebar #ffffff, hover #f1f5f9,
borders #e2e8f0, text #0f172a. Chart tooltip is made theme-aware via
inline-style interpolation since Tailwind CSS overrides cannot reach
Recharts inline styles.
2026-04-11 14:24:51 -05:00
NixReaper 4de2a7fa8e fix: auto-retry version fetch + add uptime/kill to exec allowlist
Layout.tsx: when versionError is true, automatically retry fetchVersion
after 5 s. This handles the case where the initial fetch runs during
the service-restart window after an upgrade (server not yet up), which
caused the sidebar footer to permanently show 'Version unavailable'
even though the server came back online shortly after.

exec.ts: add 'uptime' and 'kill' to ALLOWED_COMMANDS. 'uptime' was
missing, causing exec('uptime', ['-p']) in GET /api/nixserver/system/info
to throw 'Command not allowed', which rejected the entire Promise.all
and returned 500 — leaving Hostname and OS as '...' on the dashboard.
2026-04-11 14:19:24 -05:00
NixReaper 90dbf94c18 fix(layout): use authenticated api client for version/upgrade calls
Plain axios was called without the Authorization header, so the
requireAdmin middleware rejected every request with 401. The catch
block set versionError but the error state was also not yet deployed,
leaving the spinner stuck on 'Checking version…' forever.

Switch both axios.get('/api/nixserver/system/version') and
axios.post('/api/nixserver/system/upgrade') to the api client from
../api/client, which automatically attaches Bearer token from
localStorage on every request.
2026-04-11 14:15:48 -05:00
15 changed files with 261 additions and 6046 deletions
+9 -4
View File
@@ -6,15 +6,20 @@ dist/
.DS_Store
Thumbs.db
# package-lock.json is platform-specific (rollup native binaries differ between
# Windows dev machines and Linux servers). Never commit it — the server generates
# its own on each install.
package-lock.json
# Prisma
server/prisma/dev.db
server/prisma/dev.db-journal
# Build outputs
server/dist/
whm/dist/
cpanel/dist/
nixserver/dist/
nixclient/dist/
# Vite
whm/.vite/
cpanel/.vite/
nixserver/.vite/
nixclient/.vite/
+69
View File
@@ -6,6 +6,75 @@ Pre-1.0 versions treat MINOR as feature releases and PATCH as bug fixes.
---
## [0.5.2] — 2026-04-11
### Bug Fixes
- **`curl | bash` aborts immediately at confirmation prompt** — `set -euo pipefail` caused the script to exit when `read` received EOF from the pipe instead of a terminal. Fixed by reading from `/dev/tty` directly so the prompt works correctly whether the script is piped or run from a file.
---
## [0.5.1] — 2026-04-11
### Bug Fixes
- **`install.sh` build steps used `--silent`** — errors during `npm install` and frontend/server builds were suppressed, making failures invisible. Removed `--silent` from all build commands. Also added `rm -rf node_modules package-lock.json` before `npm install` to ensure a clean platform-correct install, consistent with `update.sh`.
---
## [0.5.0] — 2026-04-11
### Bug Fixes
- **Rollup native binary missing on Linux** — `package-lock.json` was generated on Windows and committed to git. It included the Windows rollup binary (`@rollup/rollup-win32-x64-msvc`) but not the Linux one (`@rollup/rollup-linux-x64-gnu`), causing the frontend build to fail on the server with `Cannot find module @rollup/rollup-linux-x64-gnu`.
- **`package-lock.json` added to `.gitignore`** — lock files are platform-specific and must never be committed. The server now generates its own fresh lock file on every update.
- **`update.sh` now does a clean install** — deletes `node_modules/` and `package-lock.json` before `npm install` on every update run to guarantee platform-correct native binaries. Also drops `--silent` from build commands so errors are visible.
- **`.gitignore` corrected** — removed stale `whm/` and `cpanel/` dist paths, added correct `nixserver/` and `nixclient/` paths.
---
## [0.4.9] — 2026-04-11
### Bug Fixes
- **`update.sh` fails when `package-lock.json` has local changes** — `git pull --ff-only` aborted with "Your local changes would be overwritten by merge" whenever npm had modified the lock file on the server. Fixed by running `git checkout -- package-lock.json` before the pull to discard local changes to the generated lock file.
---
## [0.4.8] — 2026-04-11
### Bug Fixes
- **Login page showing stale hardcoded version** — "NixServer v0.3.2 · Port 2087" was hardcoded. Now fetches version live from `GET /api/health` (no auth required) on mount.
- **Login page subtitle** — "Web Host Manager" replaced with "Admin Panel" to remove any WHM trademark association.
---
## [0.4.7] — 2026-04-11
### Bug Fixes
- **Login broken after v0.4.6 update** — `package-lock.json` still mapped the workspace as `@nixpanel/whm`. After renaming the package to `@nixpanel/nixserver`, npm workspace resolution failed during `npm install`, causing the frontend build to break and leaving the panel unresponsive. Fixed by regenerating `package-lock.json` with the correct package name.
---
## [0.4.6] — 2026-04-11
### Changes
- **Removed all WHM trademark references** — "WHM" is a registered trademark of cPanel, L.L.C. All occurrences replaced with "NixServer":
- Sidebar menu item "Basic WebHost Manager® Setup" → "Basic NixServer Setup"
- Sidebar menu item "WHM Marketplace" → "NixServer Marketplace"
- Sidebar menu item "OpenAPI Documentation for WHM" → "OpenAPI Documentation for NixServer"
- Internal npm package name `@nixpanel/whm` → `@nixpanel/nixserver`
---
## [0.4.5] — 2026-04-11
### Bug Fixes
- **Sidebar version refresh not updating** — clicking the refresh icon now clears the stale version immediately (shows spinner) and adds a `?_t=` cache-bust parameter so the browser never serves a cached response. Same fix applied to the "Retry" button in the error state.
- **`uptime` and `kill` missing from exec allowlist** — caused `GET /api/nixserver/system/info` to throw "Command not allowed", returning 500 and leaving Hostname/OS as `…` on the dashboard.
- **Version fetch auto-retry** — when the version API fails (e.g. during service restart after upgrade), the sidebar now automatically retries every 5 s until it succeeds instead of permanently showing "Version unavailable".
### New Features
- **Day/Night mode toggle** — sun/moon flip switch at the right end of the dashboard info bar. Light palette: page `#f0f4f8`, cards `#ffffff`. Preference persisted to `localStorage`. All pages switch via seven `[data-theme="light"]` CSS overrides in `index.css` without touching individual page files.
---
## [0.4.4] — 2026-04-11
### New Features
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@nixpanel/whm",
"version": "0.4.3",
"name": "@nixpanel/nixserver",
"version": "0.4.6",
"private": true,
"type": "module",
"scripts": {
+6 -3
View File
@@ -1,6 +1,7 @@
import React from 'react'
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import { AuthProvider, useAuth } from './contexts/AuthContext'
import { ThemeProvider } from './contexts/ThemeContext'
import Layout from './components/Layout'
import Login from './pages/Login'
import Dashboard from './pages/Dashboard'
@@ -233,9 +234,11 @@ function PageLoader() {
export default function App() {
return (
<BrowserRouter>
<AuthProvider>
<AppRoutes />
</AuthProvider>
<ThemeProvider>
<AuthProvider>
<AppRoutes />
</AuthProvider>
</ThemeProvider>
</BrowserRouter>
)
}
+23 -9
View File
@@ -9,8 +9,8 @@ import {
ChevronDown, ChevronRight, Search, RotateCcw, RefreshCcw,
ArrowUpCircle, CheckCircle2, AlertCircle, Loader2
} from 'lucide-react'
import axios from 'axios'
import { useAuth } from '../contexts/AuthContext'
import { api } from '../api/client'
// ─── Nav data ────────────────────────────────────────────────────────────────
@@ -28,7 +28,7 @@ const NAV: NavCategory[] = [
label: 'Server Configuration',
icon: Settings,
items: [
{ label: 'Basic WebHost Manager® Setup', to: '/settings' },
{ label: 'Basic NixServer Setup', to: '/settings' },
{ label: 'Change Root Password', to: '/security' },
{ label: 'Configure Analytics', to: '/settings' },
{ label: 'Configure Cron Jobs', to: '/settings' },
@@ -40,7 +40,7 @@ const NAV: NavCategory[] = [
{ label: 'Terminal', to: '/system' },
{ label: 'Tweak Settings', to: '/settings' },
{ label: 'Update Preferences', to: '/settings' },
{ label: 'WHM Marketplace', to: '/settings' },
{ label: 'NixServer Marketplace', to: '/settings' },
],
},
{
@@ -424,7 +424,7 @@ const NAV: NavCategory[] = [
{ label: 'Manage API Tokens', to: '/settings' },
{ label: 'Manage Hooks', to: '/settings' },
{ label: 'OpenAPI Documentation for NixPanel', to: '/settings' },
{ label: 'OpenAPI Documentation for WHM', to: '/settings' },
{ label: 'OpenAPI Documentation for NixServer', to: '/settings' },
],
},
{
@@ -465,23 +465,37 @@ export default function Layout() {
const [versionError, setVersionError] = useState(false)
const [upgradeState, setUpgradeState] = useState<'idle' | 'confirm' | 'running' | 'done' | 'error'>('idle')
const fetchVersion = useCallback(async () => {
const fetchVersion = useCallback(async (manual = false) => {
setVersionError(false)
// On a manual refresh, clear the stale value so the spinner shows
if (manual) setVersionInfo(null)
try {
const { data } = await axios.get('/api/nixserver/system/version', { timeout: 10_000 })
const { data } = await api.get('/nixserver/system/version', {
timeout: 10_000,
// Cache-bust so the browser never serves a stale version response
params: { _t: Date.now() },
})
setVersionInfo(data.data)
} catch {
setVersionError(true)
}
}, [])
// Initial fetch
useEffect(() => { fetchVersion() }, [fetchVersion])
// Auto-retry every 5 s while in error state (handles the service-restart window after an upgrade)
useEffect(() => {
if (!versionError) return
const t = setTimeout(fetchVersion, 5_000)
return () => clearTimeout(t)
}, [versionError, fetchVersion])
const handleUpgrade = async () => {
if (upgradeState === 'confirm') {
setUpgradeState('running')
try {
await axios.post('/api/nixserver/system/upgrade')
await api.post('/nixserver/system/upgrade')
setUpgradeState('done')
} catch {
setUpgradeState('error')
@@ -677,7 +691,7 @@ export default function Layout() {
)}
</div>
<button
onClick={fetchVersion}
onClick={() => fetchVersion(true)}
title="Check for updates"
className="text-[#4a5568] hover:text-[#94a3b8] transition-colors"
>
@@ -733,7 +747,7 @@ export default function Layout() {
) : versionError ? (
<div className="flex items-center justify-between px-1 py-1">
<span className="text-[#4a5568] text-[10px]">Version unavailable</span>
<button onClick={fetchVersion} title="Retry" className="text-[#4a5568] hover:text-[#94a3b8] transition-colors">
<button onClick={() => fetchVersion(true)} title="Retry" className="text-[#4a5568] hover:text-[#94a3b8] transition-colors">
<RefreshCcw size={10} />
</button>
</div>
+35
View File
@@ -0,0 +1,35 @@
import { createContext, useContext, useEffect, useState } from 'react'
type Theme = 'dark' | 'light'
interface ThemeContextValue {
theme: Theme
toggleTheme: () => void
}
const ThemeContext = createContext<ThemeContextValue | null>(null)
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>(() =>
(localStorage.getItem('nixpanel-theme') as Theme) ?? 'dark'
)
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme)
localStorage.setItem('nixpanel-theme', theme)
}, [theme])
const toggleTheme = () => setTheme(t => (t === 'dark' ? 'light' : 'dark'))
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
)
}
export function useTheme() {
const ctx = useContext(ThemeContext)
if (!ctx) throw new Error('useTheme must be used inside ThemeProvider')
return ctx
}
+42
View File
@@ -45,3 +45,45 @@ body {
::-webkit-scrollbar-thumb:hover {
background: #3a3d4e;
}
/* ── Light theme ─────────────────────────────────────────────────────────── */
[data-theme="light"] {
--bg-primary: #f0f4f8;
--bg-secondary: #ffffff;
--bg-card: #f1f5f9;
--border: #e2e8f0;
--text-primary: #0f172a;
--text-muted: #64748b;
}
[data-theme="light"] body {
background-color: #f0f4f8;
color: #0f172a;
}
/* Scrollbar */
[data-theme="light"] ::-webkit-scrollbar-track { background: #f1f5f9; }
[data-theme="light"] ::-webkit-scrollbar-thumb { background: #cbd5e1; }
[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
/* ── Background overrides ──────────────────────────────────────────────── */
/* Page background (darkest) */
[data-theme="light"] .bg-\[\#0f1117\] { background-color: #f0f4f8 !important; }
/* Cards / sidebar */
[data-theme="light"] .bg-\[\#1a1d27\] { background-color: #ffffff !important; }
/* Hover / alternate rows */
[data-theme="light"] .bg-\[\#1e2130\] { background-color: #f1f5f9 !important; }
/* ── Border overrides ──────────────────────────────────────────────────── */
[data-theme="light"] .border-\[\#2a2d3e\] { border-color: #e2e8f0 !important; }
/* ── Text overrides ────────────────────────────────────────────────────── */
[data-theme="light"] .text-white { color: #0f172a !important; }
[data-theme="light"] .text-\[\#94a3b8\] { color: #475569 !important; }
[data-theme="light"] .text-\[\#64748b\] { color: #64748b !important; }
[data-theme="light"] .text-\[\#4a5568\] { color: #94a3b8 !important; }
/* ── Input fields ──────────────────────────────────────────────────────── */
[data-theme="light"] input { color: #0f172a !important; }
[data-theme="light"] input::placeholder { color: #94a3b8 !important; }
+41 -8
View File
@@ -1,8 +1,9 @@
import React, { useEffect, useState, useRef } from 'react'
import { Users, Server, HardDrive, Activity, AlertTriangle, CheckCircle2 } from 'lucide-react'
import { Users, Server, HardDrive, Activity, AlertTriangle, CheckCircle2, Sun, Moon } from 'lucide-react'
import { AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
import { api } from '../api/client'
import { useAuth } from '../contexts/AuthContext'
import { useTheme } from '../contexts/ThemeContext'
interface SystemStats {
cpu: number
@@ -71,6 +72,7 @@ function Divider() {
export default function Dashboard() {
const { user } = useAuth()
const { theme, toggleTheme } = useTheme()
const [stats, setStats] = useState<SystemStats | null>(null)
const [accountStats, setAccountStats] = useState<AccountStats | null>(null)
const [cpuHistory, setCpuHistory] = useState<{ t: string; cpu: number; mem: number }[]>([])
@@ -164,17 +166,42 @@ export default function Dashboard() {
{/* ── cPanel-style server info bar ─────────────────────────────────── */}
<div className="bg-[#1a1d27] border border-[#2a2d3e] rounded-xl px-5 py-3.5 flex flex-wrap items-center gap-x-5 gap-y-3">
<InfoCell label="Username" value={user?.username ?? '…'} />
<InfoCell label="Username" value={user?.username ?? '…'} />
<Divider />
<InfoCell label="Hostname" value={systemInfo?.hostname ?? '…'} />
<InfoCell label="Hostname" value={systemInfo?.hostname ?? '…'} />
<Divider />
<InfoCell label="OS" value={systemInfo?.os ?? '…'} />
<InfoCell label="OS" value={systemInfo?.os ?? '…'} />
<Divider />
<InfoCell label="NixPanel Version" value={version ? `v${version}` : '…'} />
<InfoCell label="NixPanel Version" value={version ? `v${version}` : '…'} />
<Divider />
<InfoCell label="Load Averages" value={loadStr} />
<InfoCell label="Load Averages" value={loadStr} />
<Divider />
<InfoCell label="Server Monitoring" value={monitoringNode} />
{/* ── Day / Night toggle — far right ── */}
<div className="ml-auto flex items-center gap-2 flex-shrink-0">
<Moon
size={13}
className={theme === 'dark' ? 'text-indigo-400' : 'text-[#94a3b8]'}
/>
<button
onClick={toggleTheme}
title={theme === 'dark' ? 'Switch to day mode' : 'Switch to night mode'}
className={`relative flex items-center w-10 h-5 rounded-full transition-colors cursor-pointer flex-shrink-0 ${
theme === 'light' ? 'bg-amber-400' : 'bg-[#2a2d3e]'
}`}
>
<span
className={`absolute h-4 w-4 rounded-full bg-white shadow-sm transition-transform ${
theme === 'light' ? 'translate-x-5' : 'translate-x-0.5'
}`}
/>
</button>
<Sun
size={13}
className={theme === 'light' ? 'text-amber-500' : 'text-[#4a5568]'}
/>
</div>
</div>
{/* ── Stat cards ───────────────────────────────────────────────────── */}
@@ -210,8 +237,14 @@ export default function Dashboard() {
<XAxis dataKey="t" tick={{ fill: '#64748b', fontSize: 10 }} tickLine={false} axisLine={false} />
<YAxis domain={[0, 100]} tick={{ fill: '#64748b', fontSize: 10 }} tickLine={false} axisLine={false} />
<Tooltip
contentStyle={{ background: '#1e2130', border: '1px solid #2a2d3e', borderRadius: 8, fontSize: 12 }}
labelStyle={{ color: '#94a3b8' }}
contentStyle={{
background: theme === 'light' ? '#ffffff' : '#1e2130',
border: `1px solid ${theme === 'light' ? '#e2e8f0' : '#2a2d3e'}`,
borderRadius: 8,
fontSize: 12,
color: theme === 'light' ? '#0f172a' : '#e2e8f0',
}}
labelStyle={{ color: theme === 'light' ? '#64748b' : '#94a3b8' }}
/>
<Area type="monotone" dataKey="cpu" stroke="#6366f1" fill="url(#cpu)" strokeWidth={2} name="CPU %" />
<Area type="monotone" dataKey="mem" stroke="#10b981" fill="url(#mem)" strokeWidth={2} name="Mem %" />
+11 -3
View File
@@ -1,7 +1,8 @@
import React, { useState } from 'react'
import React, { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { useAuth } from '../contexts/AuthContext'
import { Shield } from 'lucide-react'
import axios from 'axios'
export default function Login() {
const { login } = useAuth()
@@ -10,6 +11,13 @@ export default function Login() {
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
const [version, setVersion] = useState<string | null>(null)
useEffect(() => {
axios.get('/api/health', { timeout: 5_000 })
.then(r => setVersion(r.data.version ?? null))
.catch(() => {})
}, [])
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
@@ -35,7 +43,7 @@ export default function Login() {
<Shield size={24} className="text-white" />
</div>
<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">Admin Panel</p>
</div>
{/* Form */}
@@ -81,7 +89,7 @@ export default function Login() {
</form>
<p className="text-center text-[#64748b] text-xs mt-4">
NixServer v0.3.2 · Port 2087
NixServer{version ? ` v${version}` : ''} · Port 2087
</p>
</div>
</div>
-6004
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "nixpanel-server",
"version": "0.4.4",
"version": "0.5.2",
"private": true,
"workspaces": [
"server",
+8 -5
View File
@@ -82,6 +82,8 @@ if [[ -z "$ADMIN_EMAIL" ]]; then
ADMIN_EMAIL="admin@$(hostname -f 2>/dev/null || echo localhost)"
fi
# Redirect all interactive reads to /dev/tty so curl | bash works correctly
MARIADB_ROOT_PASS=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)
JWT_SECRET=$(gen_secret)
JWT_REFRESH_SECRET=$(gen_secret)
@@ -93,7 +95,7 @@ echo " Admin username : $ADMIN_USER"
echo " Admin email : $ADMIN_EMAIL"
echo " Admin password : $([ "$GENERATED_PASS" = "true" ] && echo "<auto-generated>" || echo "$ADMIN_PASS")"
echo ""
read -r -p "Continue? [Y/n] " CONFIRM
read -r -p "Continue? [Y/n] " CONFIRM < /dev/tty || CONFIRM="Y"
CONFIRM="${CONFIRM:-Y}"
[[ "$CONFIRM" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; }
@@ -547,7 +549,8 @@ success ".env written"
# ── npm install ───────────────────────────────────────────────────────────────
step "Installing Node.js dependencies"
cd "$INSTALL_DIR"
npm install --silent
rm -rf node_modules package-lock.json
npm install
success "npm dependencies installed"
# ── Prisma generate + db push ─────────────────────────────────────────────────
@@ -571,10 +574,10 @@ success "Admin account and default packages seeded"
# ── Build ─────────────────────────────────────────────────────────────────────
step "Building frontends and server"
cd "$INSTALL_DIR"
npm run build --workspace=nixserver --silent
npm run build --workspace=nixclient --silent
npm run build --workspace=nixserver
npm run build --workspace=nixclient
cd "$INSTALL_DIR/server"
npm run build --silent
npm run build
success "Build complete"
# ── Set web-readable permissions on frontend assets ───────────────────────────
+11 -4
View File
@@ -14,12 +14,19 @@ INSTALL_DIR="${1:-/opt/nixpanel}"
[[ -d "$INSTALL_DIR/.git" ]] || error "NixPanel not found at $INSTALL_DIR"
step "Pulling latest code"
# package-lock.json is not tracked (platform-specific), but discard any other
# locally modified tracked files that could block a fast-forward pull
git -C "$INSTALL_DIR" checkout -- . 2>/dev/null || true
git -C "$INSTALL_DIR" pull --ff-only
success "Code updated"
step "Installing dependencies"
cd "$INSTALL_DIR"
npm install --workspaces --include-workspace-root --silent
# Delete node_modules and any stale lock file so npm regenerates them fresh
# for this platform (avoids the rollup native binary mismatch between
# Windows dev machines and Linux servers)
rm -rf node_modules package-lock.json
npm install --workspaces --include-workspace-root
success "Dependencies ready"
step "Running migrations"
@@ -30,8 +37,8 @@ success "Migrations applied"
step "Rebuilding frontends"
cd "$INSTALL_DIR"
npm run build --workspace=nixserver --silent
npm run build --workspace=nixclient --silent
npm run build --workspace=nixserver
npm run build --workspace=nixclient
# Ensure Apache (www-data) can read the rebuilt assets
chmod 755 "$INSTALL_DIR/nixserver" "$INSTALL_DIR/nixclient"
find "$INSTALL_DIR/nixserver/dist" -type d -exec chmod 755 {} \;
@@ -42,7 +49,7 @@ success "Frontends rebuilt"
step "Rebuilding server"
cd "$INSTALL_DIR/server"
npm run build --silent 2>/dev/null || npx tsc 2>/dev/null || true
npm run build 2>/dev/null || npx tsc 2>/dev/null || true
success "Server rebuilt"
step "Restarting service"
+2 -2
View File
@@ -60,7 +60,7 @@ export async function buildApp() {
// ─── Static files ────────────────────────────────────────────────────────────
// NixServer frontend
const whmDist = path.resolve(__dirname, '../../nixserver/dist')
const nixserverDist = path.resolve(__dirname, '../../nixserver/dist')
const cpanelDist = path.resolve(__dirname, '../../nixclient/dist')
// ─── API Routes ──────────────────────────────────────────────────────────────
@@ -73,7 +73,7 @@ export async function buildApp() {
fastify.get('/api/health', async () => ({
status: 'ok',
version: '0.4.4',
version: '0.5.2',
timestamp: new Date().toISOString(),
}))
+1 -1
View File
@@ -24,7 +24,7 @@ const ALLOWED_COMMANDS = new Set([
// Network
'ss', 'ip', 'hostname',
// System
'uname', 'free', 'ps',
'uname', 'uptime', 'free', 'ps', 'kill',
// PHP
'php8.2', 'php8.3',
// Fail2ban