8 Commits
Author SHA1 Message Date
NixReaper b8ac6ee515 fix: resolve server TS errors — jwt type augmentation, sign options, services import
bump version to 0.3.1
2026-04-05 12:13:06 -05:00
NixReaper 2fce30eddc chore: bump version to 0.3.0 2026-04-05 12:09:04 -05:00
NixReaper 8c64cd5d93 fix: remove unused TS imports and fix icon size type in nixclient 2026-04-05 12:07:25 -05:00
NixReaper f369732f6f fix: remove unused TS imports, fix icon size type, use prisma db push 2026-04-05 12:03:33 -05:00
NixReaper 5f42fe1ef0 fix: use CREATE OR REPLACE USER so DB password always matches .env on reinstall 2026-04-05 11:54:40 -05:00
NixReaper 54af40a26f fix: remove invalid --silent flag from prisma generate 2026-04-05 11:48:44 -05:00
NixReaper 8bd2ba5c91 feat: add install log tee to /var/log/nixpanel-install.log 2026-04-05 11:48:07 -05:00
NixReaper aaa6d6ce46 fix: installer banner version and admin password display
- Fix version banner showing 0.1.0 instead of 0.2.0
- Fix admin password line printing "<auto-generated>true" when
  GENERATED_PASS=true due to broken parameter expansion — replace
  with explicit if/else
2026-04-05 11:37:53 -05:00
28 changed files with 60 additions and 50 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@nixpanel/cpanel", "name": "@nixpanel/cpanel",
"version": "0.2.0", "version": "0.3.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useState } from 'react' import { useState } from 'react'
import { Outlet, NavLink, useNavigate } from 'react-router-dom' import { Outlet, NavLink, useNavigate } from 'react-router-dom'
import { import {
LayoutDashboard, FolderOpen, Database, Mail, Lock, LayoutDashboard, FolderOpen, Database, Mail, Lock,
+1 -1
View File
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { Clock, Plus, Trash2, RefreshCw, ToggleLeft, ToggleRight } from 'lucide-react' import { Plus, Trash2, RefreshCw, ToggleLeft, ToggleRight } from 'lucide-react'
import { api } from '../api/client' import { api } from '../api/client'
interface CronJob { id: number; schedule: string; command: string; comment: string | null; enabled: boolean } interface CronJob { id: number; schedule: string; command: string; comment: string | null; enabled: boolean }
+1 -1
View File
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { Folder, FileText, ChevronRight, Trash2, Pencil, Plus, Upload, RefreshCw, FolderPlus, Save, X } from 'lucide-react' import { Folder, FileText, ChevronRight, Trash2, Pencil, RefreshCw, FolderPlus, Save, X } from 'lucide-react'
import { api } from '../api/client' import { api } from '../api/client'
interface FileEntry { name: string; type: 'file' | 'directory'; size: number; mtime: string; path: string } interface FileEntry { name: string; type: 'file' | 'directory'; size: number; mtime: string; path: string }
+1 -1
View File
@@ -49,7 +49,7 @@ export default function Login() {
</button> </button>
</form> </form>
<p className="text-center text-[#64748b] text-xs mt-4">NixClient v0.2.0 · Port 2083</p> <p className="text-center text-[#64748b] text-xs mt-4">NixClient v0.3.0 · Port 2083</p>
</div> </div>
</div> </div>
) )
+2 -2
View File
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { Database, Mail, Globe, FolderOpen, Lock, Anchor, Clock, HardDrive } from 'lucide-react' import { Database, Mail, Globe, Lock, Anchor, Clock } from 'lucide-react'
import { api } from '../api/client' import { api } from '../api/client'
import { useAuth } from '../contexts/AuthContext' import { useAuth } from '../contexts/AuthContext'
@@ -34,7 +34,7 @@ function UsageBar({ used, limit, label }: { used: number; limit: number; label:
) )
} }
function CountCard({ icon: Icon, label, used, limit, to }: { icon: React.ComponentType<{ size?: number; className?: string }>; label: string; used: number; limit: number; to: string }) { function CountCard({ icon: Icon, label, used, limit, to }: { icon: React.ComponentType<{ size?: number | string; className?: string }>; label: string; used: number; limit: number; to: string }) {
return ( return (
<a href={to} className="bg-[#111827] border border-[#1e2d45] rounded-xl p-4 hover:border-sky-500/30 transition-colors block"> <a href={to} className="bg-[#111827] border border-[#1e2d45] rounded-xl p-4 hover:border-sky-500/30 transition-colors block">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Lock, RefreshCw, RotateCcw } from 'lucide-react' import { Lock, RefreshCw, RotateCcw } from 'lucide-react'
import { api } from '../api/client' import { api } from '../api/client'
+1 -1
View File
@@ -8,7 +8,7 @@ interface Stats {
counts: Record<string, number> counts: Record<string, number>
} }
function Bar({ label, used, limit, icon: Icon }: { label: string; used: number; limit: number; icon: React.ComponentType<{ size?: number; className?: string }> }) { function Bar({ label, used, limit, icon: Icon }: { label: string; used: number; limit: number; icon: React.ComponentType<{ size?: number | string; className?: string }> }) {
const pct = limit === 0 ? 0 : Math.min(100, Math.round((used / limit) * 100)) const pct = limit === 0 ? 0 : Math.min(100, Math.round((used / limit) * 100))
const color = pct > 90 ? 'bg-red-500' : pct > 70 ? 'bg-amber-500' : 'bg-sky-500' const color = pct > 90 ? 'bg-red-500' : pct > 70 ? 'bg-amber-500' : 'bg-sky-500'
return ( return (
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@nixpanel/whm", "name": "@nixpanel/whm",
"version": "0.2.0", "version": "0.3.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+2 -2
View File
@@ -1,8 +1,8 @@
import React, { useState } from 'react' import { useState } from 'react'
import { Outlet, NavLink, useNavigate } from 'react-router-dom' import { Outlet, NavLink, useNavigate } from 'react-router-dom'
import { import {
LayoutDashboard, Users, Package, UserCog, Globe, Mail, Shield, LayoutDashboard, Users, Package, UserCog, Globe, Mail, Shield,
Server, Database, HardDrive, Settings, LogOut, Menu, X, Server, HardDrive, Settings, LogOut, Menu, X,
Activity, Lock, BarChart3, Cpu Activity, Lock, BarChart3, Cpu
} from 'lucide-react' } from 'lucide-react'
import { useAuth } from '../contexts/AuthContext' import { useAuth } from '../contexts/AuthContext'
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState, useCallback } from 'react' import { useEffect, useState, useCallback } from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { Plus, Search, MoreVertical, UserX, UserCheck, Trash2, RefreshCw } from 'lucide-react' import { Plus, Search, MoreVertical, UserX, UserCheck, Trash2, RefreshCw } from 'lucide-react'
import { api } from '../api/client' import { api } from '../api/client'
+2 -2
View File
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { HardDrive, Download, Trash2, RefreshCw, Play } from 'lucide-react' import { HardDrive, Trash2, RefreshCw, Play } from 'lucide-react'
import { api } from '../api/client' import { api } from '../api/client'
interface Backup { path: string; filename: string; size: number; createdAt: string } interface Backup { path: string; filename: string; size: number; createdAt: string }
+2 -2
View File
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { RefreshCw, Globe, Plus, Trash2, Pencil } from 'lucide-react' import { RefreshCw, Globe, Plus, Trash2 } from 'lucide-react'
import { api } from '../api/client' import { api } from '../api/client'
interface Zone { id: number; domain: string; status: string; account: { username: string }; _count: { records: number } } interface Zone { id: number; domain: string; status: string; account: { username: string }; _count: { records: number } }
+2 -2
View File
@@ -1,5 +1,5 @@
import React, { useEffect, useState, useRef } from 'react' import React, { useEffect, useState, useRef } from 'react'
import { Users, Globe, Server, HardDrive, Activity, AlertTriangle } from 'lucide-react' import { Users, Server, HardDrive, Activity } from 'lucide-react'
import { AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts' import { AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
import { api } from '../api/client' import { api } from '../api/client'
@@ -22,7 +22,7 @@ interface StatCard {
label: string label: string
value: string | number value: string | number
sub?: string sub?: string
icon: React.ComponentType<{ size?: number; className?: string }> icon: React.ComponentType<{ size?: number | string; className?: string }>
color: string color: string
} }
+2 -2
View File
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Mail, Plus, Trash2, RefreshCw } from 'lucide-react' import { Plus, Trash2, RefreshCw } from 'lucide-react'
import { api } from '../api/client' import { api } from '../api/client'
interface EmailAccount { id: number; address: string; domain: string; quotaMb: number; usedMb: number; status: string } interface EmailAccount { id: number; address: string; domain: string; quotaMb: number; usedMb: number; status: string }
+1 -1
View File
@@ -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">
NixServer v0.2.0 · Port 2087 NixServer v0.3.0 · Port 2087
</p> </p>
</div> </div>
</div> </div>
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Cpu, CheckCircle, XCircle, RefreshCw } from 'lucide-react' import { Cpu, CheckCircle, XCircle, RefreshCw } from 'lucide-react'
import { api } from '../api/client' import { api } from '../api/client'
+2 -5
View File
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { Shield, Plus, Trash2, CheckCircle } from 'lucide-react' import { Plus, Trash2, CheckCircle } from 'lucide-react'
import { api } from '../api/client' import { api } from '../api/client'
interface BlockedIp { id: number; ip: string; reason: string | null; createdAt: string } interface BlockedIp { id: number; ip: string; reason: string | null; createdAt: string }
@@ -16,10 +16,7 @@ export default function Security() {
const [events, setEvents] = useState<SecurityEvent[]>([]) const [events, setEvents] = useState<SecurityEvent[]>([])
const [newIp, setNewIp] = useState('') const [newIp, setNewIp] = useState('')
const [reason, setReason] = useState('') const [reason, setReason] = useState('')
const [loading, setLoading] = useState(false)
const fetch = async () => { const fetch = async () => {
setLoading(true)
try { try {
const [b, e] = await Promise.all([ const [b, e] = await Promise.all([
api.get('/nixserver/security/blocked-ips'), api.get('/nixserver/security/blocked-ips'),
@@ -27,7 +24,7 @@ export default function Security() {
]) ])
setBlocked(b.data.data) setBlocked(b.data.data)
setEvents(e.data.data.items ?? e.data.data) setEvents(e.data.data.items ?? e.data.data)
} finally { setLoading(false) } } catch {}
} }
useEffect(() => { fetch() }, []) useEffect(() => { fetch() }, [])
+2 -2
View File
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Play, Square, RefreshCw, AlertCircle, CheckCircle, MinusCircle } from 'lucide-react' import { RefreshCw, AlertCircle, CheckCircle, MinusCircle } from 'lucide-react'
import { api } from '../api/client' import { api } from '../api/client'
interface Service { name: string; status: 'running' | 'stopped' | 'failed' | 'unknown'; enabled: boolean } interface Service { name: string; status: 'running' | 'stopped' | 'failed' | 'unknown'; enabled: boolean }
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { RefreshCw } from 'lucide-react' import { RefreshCw } from 'lucide-react'
import { api } from '../api/client' import { api } from '../api/client'
+2 -2
View File
@@ -1,10 +1,10 @@
import React, { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Server, RefreshCw, RotateCcw, CheckCircle, XCircle } from 'lucide-react' import { Server, RefreshCw, RotateCcw, CheckCircle, XCircle } from 'lucide-react'
import { api } from '../api/client' import { api } from '../api/client'
export default function WebServer() { export default function WebServer() {
const [status, setStatus] = useState<{ nginx: { active: boolean }; apache: { active: boolean } } | null>(null) const [status, setStatus] = useState<{ nginx: { active: boolean }; apache: { active: boolean } } | null>(null)
const [vhosts, setVhosts] = useState<{ domain: string; path: string }[]>([]) const [, setVhosts] = useState<{ domain: string; path: string }[]>([])
const [logs, setLogs] = useState<{ lines: string[] } | null>(null) const [logs, setLogs] = useState<{ lines: string[] } | null>(null)
const [logType, setLogType] = useState<'access' | 'error'>('access') const [logType, setLogType] = useState<'access' | 'error'>('access')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "nixpanel-server", "name": "nixpanel-server",
"version": "0.2.0", "version": "0.3.0",
"private": true, "private": true,
"workspaces": [ "workspaces": [
"server", "server",
+18 -5
View File
@@ -28,7 +28,7 @@ cat <<'EOF'
Open-Source Web Hosting Control Panel Open-Source Web Hosting Control Panel
EOF EOF
echo -e "${RESET}" echo -e "${RESET}"
echo -e " Version : ${BOLD}0.1.0${RESET}" echo -e " Version : ${BOLD}0.3.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 " NixServer: ${BOLD}https://<your-ip>:2087${RESET}" echo -e " NixServer: ${BOLD}https://<your-ip>:2087${RESET}"
echo -e " NixClient: ${BOLD}https://<your-ip>:2083${RESET}" echo -e " NixClient: ${BOLD}https://<your-ip>:2083${RESET}"
@@ -121,12 +121,24 @@ echo -e "${BOLD}Installation summary:${RESET}"
echo " Install directory : $INSTALL_DIR" echo " Install directory : $INSTALL_DIR"
echo " Admin username : $ADMIN_USER" echo " Admin username : $ADMIN_USER"
echo " Admin email : $ADMIN_EMAIL" echo " Admin email : $ADMIN_EMAIL"
echo " Admin password : ${GENERATED_PASS:+<auto-generated, shown at end>}${GENERATED_PASS:-$ADMIN_PASS}" if [[ "$GENERATED_PASS" == "true" ]]; then
echo " Admin password : <auto-generated, shown at end>"
else
echo " Admin password : $ADMIN_PASS"
fi
echo "" echo ""
read -r -p "Continue? [Y/n] " CONFIRM read -r -p "Continue? [Y/n] " CONFIRM
CONFIRM="${CONFIRM:-Y}" CONFIRM="${CONFIRM:-Y}"
[[ "$CONFIRM" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; } [[ "$CONFIRM" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; }
# ── Logging ───────────────────────────────────────────────────────────────────
LOG_FILE="/var/log/nixpanel-install.log"
exec > >(tee -a "$LOG_FILE") 2>&1
echo "=================================================="
echo " NixPanel Install Log — $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo " OS: ${PRETTY_NAME:-$OS_ID} Arch: $(uname -m)"
echo "=================================================="
# ── Update package index ────────────────────────────────────────────────────── # ── Update package index ──────────────────────────────────────────────────────
step "Updating package index" step "Updating package index"
if [[ "$PKG_MGR" == "apt" ]]; then if [[ "$PKG_MGR" == "apt" ]]; then
@@ -205,7 +217,7 @@ mariadb -u root <<SQL
CREATE DATABASE IF NOT EXISTS \`${NIXPANEL_DB_NAME}\` CREATE DATABASE IF NOT EXISTS \`${NIXPANEL_DB_NAME}\`
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS '${NIXPANEL_DB_USER}'@'localhost' CREATE OR REPLACE USER '${NIXPANEL_DB_USER}'@'localhost'
IDENTIFIED BY '${NIXPANEL_DB_PASS}'; IDENTIFIED BY '${NIXPANEL_DB_PASS}';
GRANT ALL PRIVILEGES ON \`${NIXPANEL_DB_NAME}\`.* TO '${NIXPANEL_DB_USER}'@'localhost'; GRANT ALL PRIVILEGES ON \`${NIXPANEL_DB_NAME}\`.* TO '${NIXPANEL_DB_USER}'@'localhost';
@@ -382,9 +394,9 @@ success "npm dependencies installed"
step "Running database migrations" step "Running database migrations"
cd "$INSTALL_DIR/server" cd "$INSTALL_DIR/server"
npx prisma generate --silent npx prisma generate
DATABASE_URL="mysql://${NIXPANEL_DB_USER}:${NIXPANEL_DB_PASS}@localhost:3306/${NIXPANEL_DB_NAME}" \ DATABASE_URL="mysql://${NIXPANEL_DB_USER}:${NIXPANEL_DB_PASS}@localhost:3306/${NIXPANEL_DB_NAME}" \
npx prisma migrate deploy npx prisma db push
success "Database schema applied" success "Database schema applied"
# ── Seed default data ───────────────────────────────────────────────────────── # ── Seed default data ─────────────────────────────────────────────────────────
@@ -653,6 +665,7 @@ echo ""
echo -e " ${BOLD}Files${RESET}" echo -e " ${BOLD}Files${RESET}"
echo -e " Install : ${INSTALL_DIR}" echo -e " Install : ${INSTALL_DIR}"
echo -e " Config : ${INSTALL_DIR}/.env" echo -e " Config : ${INSTALL_DIR}/.env"
echo -e " Install log: ${LOG_FILE}"
echo -e " Service : systemctl status nixpanel" echo -e " Service : systemctl status nixpanel"
echo -e " Logs : journalctl -u nixpanel -f" echo -e " Logs : journalctl -u nixpanel -f"
echo "" echo ""
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@nixpanel/server", "name": "@nixpanel/server",
"version": "0.2.0", "version": "0.3.0",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",
+1 -1
View File
@@ -73,7 +73,7 @@ export async function buildApp() {
fastify.get('/api/health', async () => ({ fastify.get('/api/health', async () => ({
status: 'ok', status: 'ok',
version: '0.2.0', version: '0.3.0',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
})) }))
+3 -4
View File
@@ -28,7 +28,7 @@ function signTokens(
const refreshToken = fastify.jwt.sign( const refreshToken = fastify.jwt.sign(
{ ...payload, type: 'refresh' }, { ...payload, type: 'refresh' },
{ secret: config.jwt.refreshSecret, expiresIn: config.jwt.refreshExpiresIn } { expiresIn: config.jwt.refreshExpiresIn }
) )
return { accessToken, refreshToken } return { accessToken, refreshToken }
@@ -157,8 +157,7 @@ export default async function authRoutes(fastify: FastifyInstance) {
let payload: JwtPayload & { type?: string } let payload: JwtPayload & { type?: string }
try { try {
payload = fastify.jwt.verify<JwtPayload & { type?: string }>( payload = fastify.jwt.verify<JwtPayload & { type?: string }>(
body.data.refreshToken, body.data.refreshToken
{ secret: config.jwt.refreshSecret }
) )
} catch { } catch {
return reply.code(401).send({ success: false, error: 'Invalid or expired refresh token' }) return reply.code(401).send({ success: false, error: 'Invalid or expired refresh token' })
@@ -183,7 +182,7 @@ export default async function authRoutes(fastify: FastifyInstance) {
const accessToken = fastify.jwt.sign(newPayload, { expiresIn: config.jwt.expiresIn }) const accessToken = fastify.jwt.sign(newPayload, { expiresIn: config.jwt.expiresIn })
const newRefresh = fastify.jwt.sign( const newRefresh = fastify.jwt.sign(
{ ...newPayload, type: 'refresh' }, { ...newPayload, type: 'refresh' },
{ secret: config.jwt.refreshSecret, expiresIn: config.jwt.refreshExpiresIn } { expiresIn: config.jwt.refreshExpiresIn }
) )
const expiresAt = new Date() const expiresAt = new Date()
+1 -1
View File
@@ -1,7 +1,7 @@
import type { FastifyInstance } from 'fastify' import type { FastifyInstance } from 'fastify'
import { z } from 'zod' import { z } from 'zod'
import { requireAdmin } from '../../middleware/auth.js' import { requireAdmin } from '../../middleware/auth.js'
import { exec, MANAGED_SERVICES as _ } from '../../core/exec.js' import { exec } from '../../core/exec.js'
import { getServiceStatus, MANAGED_SERVICES } from '../../core/system.js' import { getServiceStatus, MANAGED_SERVICES } from '../../core/system.js'
const ServiceActionSchema = z.object({ const ServiceActionSchema = z.object({
+5 -4
View File
@@ -10,11 +10,12 @@ export interface JwtPayload {
exp?: number exp?: number
} }
// ─── Fastify type augmentation ──────────────────────────────────────────────── // ─── @fastify/jwt type augmentation ───────────────────────────────────────────
declare module 'fastify' { declare module '@fastify/jwt' {
interface FastifyRequest { interface FastifyJWT {
user?: JwtPayload payload: JwtPayload
user: JwtPayload
} }
} }