You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Game server management dashboard for the ZeroHost platform. Provides user-facing server lifecycle management (creation, renewal, suspension, deletion) backed by a Pyrodactyl/Pterodactyl panel API.
flowchart TB
subgraph Client["Client Layer"]
A["Browser (Vanilla JS SPA)"]
B["Cap CAPTCHA Widget"]
end
subgraph CDN["CDN Layer"]
C["jsDelivr\n(cap-widget)"]
D["Google Fonts"]
end
subgraph App["Application Layer (Express)"]
E["server.js\n(Entry Point)"]
F["middleware/auth.js\n(JWT Auth)"]
G["config/migrate.js\n(Schema Migration)"]
H["services/scheduler.js\n(Expiry Cron)"]
end
subgraph Routes["Route Handlers"]
I["routes/auth.js"]
J["routes/servers.js"]
end
subgraph Services["Service Layer"]
K["services/pyrodactyl.js\n(Panel API Wrapper)"]
L["services/activity.js\n(Activity Logger)"]
end
subgraph Config["Configuration"]
M["config/db.js\n(MariaDB Pool)"]
N["config/pyrodactyl.js\n(Panel Config & Limits)"]
O["config/cap.js\n(CAPTCHA Client)"]
end
subgraph External["External Systems"]
P["Pyrodactyl Panel\n(panel.zero-host.org)"]
Q["MariaDB"]
R["ip-api.com\n(VPN/Proxy Detection)"]
end
A -- "HTTP(S)" --> E
B -- "CAPTCHA Token" --> E
C -- "Web Component" --> A
D -- "Font Assets" --> A
E --> F
E --> G
E --> H
E --> I
E --> J
I --> K
I --> L
I --> O
J --> K
J --> L
J --> O
K --> P
K --> Q
L --> Q
I --> R
M --> Q
Loading
Tech Stack
Component
Technology
Runtime
Node.js >= 18 (ES Modules)
HTTP Framework
Express 4.21
Database
MariaDB via mariadb driver 3.4
Authentication
JWT (HS256, jsonwebtoken 9)
Password Hashing
Argon2id (argon2 0.41)
Frontend
Vanilla JavaScript (SPA, no framework)
Styling
Custom CSS
CAPTCHA
Self-hosted Cap CAPTCHA
Panel API
Pterodactyl / Pyrodactyl Application API
Process Manager
PM2
CI/CD
GitHub Actions (SSH deploy)
Project Structure
flowchart LR
subgraph Root["/"]
direction TB
sv["server.js\n(Express entry)"]
env[".env"]
pkg["package.json"]
pt["port.txt"]
end
subgraph Routes["routes/"]
auth["auth.js\nAuth endpoints"]
srv["servers.js\nServer CRUD"]
end
subgraph Middleware["middleware/"]
mw["auth.js\nJWT verification"]
end
subgraph Services["services/"]
p["pyrodactyl.js\nPanel API wrapper"]
a["activity.js\nActivity log"]
sc["scheduler.js\nExpiry cron"]
end
subgraph Config["config/"]
db["db.js\nMariaDB pool"]
mg["migrate.js\nSchema migrator"]
cp["cap.js\nCAPTCHA client"]
py["pyrodactyl.js\nPanel config"]
end
subgraph Public["public/"]
html["index.html\nShell"]
css["css/style.css\nStyles"]
js["js/app.js\nSPA frontend"]
end
subgraph GH["github/workflows/"]
beta["deploy-beta.yml"]
main["deploy-main.yml"]
end
Root --> Routes
Root --> Services
Root --> Config
Root --> Public
Root --> GH
sequenceDiagram
participant Browser
participant Express
participant Cap as "Cap CAPTCHA"
participant ipapi as "ip-api.com"
participant DB as "MariaDB"
participant Panel as "Pyrodactyl Panel"
Browser->>Express: POST /api/auth/register
Note over Browser,Express: Body: { email, username, password, capToken }
Express->>Cap: POST /siteverify (capToken)
Cap-->>Express: { success: boolean }
alt CAPTCHA Failed
Express-->>Browser: 400 { error: "CAPTCHA verification failed" }
end
Express->>ipapi: GET /json/{clientIP}
ipapi-->>Express: { proxy: boolean, ... }
alt Proxy / VPN Detected
Express-->>Browser: 403 { error: "VPN/Proxy detected" }
end
Express->>DB: SELECT IP count for client IP
DB-->>Express: count
alt 2+ accounts from same IP
Express-->>Browser: 403 { error: "Maximum 2 accounts per IP" }
end
Express->>Express: argon2.hash(password)
Express->>Panel: POST /api/application/users
Panel-->>Express: { attributes: { id, uuid } }
Express->>DB: INSERT INTO users (...)
DB-->>Express: OK
Express->>DB: INSERT INTO user_ips (...)
DB-->>Express: OK
Express->>Express: jwt.sign({ userId, email, username, pteroId })
Express->>DB: INSERT INTO activity_log (action='account_registered')
Express-->>Browser: 201 { token, user } + Set-Cookie: token
Loading
Login
sequenceDiagram
participant Browser
participant Express
participant DB as "MariaDB"
participant ipapi as "ip-api.com"
Browser->>Express: POST /api/auth/login
Express->>Express: CAPTCHA verification
Express->>ipapi: VPN / Proxy check
ipapi-->>Express: result
alt Blocked
Express-->>Browser: 403
end
Express->>DB: SELECT * FROM users WHERE email = ?
DB-->>Express: user row
Express->>Express: argon2.verify(hash, password)
alt Invalid Password
Express-->>Browser: 401 { error: "Invalid credentials" }
end
Express->>Express: jwt.sign({ userId, email, username, pteroId })
Express-->>Browser: 200 { token, user } + Set-Cookie: token
Loading
Server Lifecycle
stateDiagram-v2
[*] --> Creating: POST /api/servers/create
Creating --> Active: Pyrodactyl API success
Creating --> Failed: API error
Active --> Suspended: Scheduler (expired)
Active --> Suspended: Manual suspend (Pyrodactyl)
Active --> Renewed: POST /api/servers/renew/:id
Active --> Deleted: DELETE /api/servers/:id
Suspended --> Active: Renew (within 7d window)
Suspended --> Expired: 7 days past expiry
Suspended --> Deleted: DELETE /api/servers/:id
Renewed --> Active: expires_at += 90 days
Expired --> [*]
Failed --> [*]
Deleted --> [*]
Loading
Server Default Limits
Resource
Value
RAM
512 MB
CPU
50%
Disk
3 GB
Swap
0
Backups
1
Allocations
1
Renewal Policy
Servers expire 90 days after creation or last renewal.
Renewal is only permitted within a 7-day window before or after the expiration date.
Renewing an expired server automatically unsuspends it.
Scheduler
The scheduler (services/scheduler.js) runs daily at midnight via setInterval:
flowchart TD
A["Scheduler Start"] --> B["Hourly check:\nis it midnight?"]
B -->|Yes| C["SELECT * FROM server_meta\nWHERE expires_at < NOW()\nAND status = 'active'"]
C --> D["For each expired server:"]
D --> E["POST /api/application/servers/{id}/suspend"]
E --> F["UPDATE server_meta\nSET status = 'suspended'"]
F --> G["INSERT INTO activity_log\naction = 'server_suspended'"]
B -->|No| B
Loading
Configuration
Environment Variables (.env)
Variable
Description
JWT_SECRET
Secret key for JWT signing (HS256)
JWT_EXPIRES_IN
Token expiry duration (default: 2h)
COOKIE_SECRET
Secret for signed cookies
DB_HOST
MariaDB host
DB_USER
MariaDB user
DB_PASSWORD
MariaDB password
DB_NAME
MariaDB database name
DB_PORT
MariaDB port (default: 3306)
PTERO_URL
Pyrodactyl panel base URL
PTERO_API_KEY
Pyrodactyl Application API key
CAP_ENDPOINT
Cap CAPTCHA endpoint URL
CAP_SECRET
Cap CAPTCHA secret key
NODE_ENV
production or development
Rate Limiting
Scope
Window
Max Requests
Auth endpoints (login, register)
15 minutes
10
General API
60 seconds
100
Deployment
flowchart LR
subgraph Dev["Development"]
A["git push beta"]
end
subgraph CI["GitHub Actions"]
B["Checkout"]
C["Install deps\n(npm ci)"]
D["SSH Deploy"]
E["PM2 Restart"]
end
subgraph Prod["Production Server"]
F["Pull from beta/main"]
G["npm install --production"]
H["PM2 reload"]
I["DB migration\n(auto on startup)"]
end
A --> B --> C --> D --> E
E --> F --> G --> H --> I
Loading
Two deployment workflows are configured:
deploy-beta.yml: Triggered on push to beta branch.
deploy-main.yml: Triggered on push to main branch.
Both use SSH credentials configured as GitHub repository secrets (SSH_HOST, SSH_USER, SSH_PASSWORD, SSH_PORT).
About
The dashboard for controlling our Pyrodactyl Panel (for creating accounts, creating servers and more)