-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsetup.php
More file actions
132 lines (118 loc) · 6.96 KB
/
Copy pathsetup.php
File metadata and controls
132 lines (118 loc) · 6.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
<?php
declare(strict_types=1);
require_once __DIR__ . '/functions.php';
setup_guard_or_redirect();
$error = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
if (!rate_limit('setup', 8)) {
throw new RuntimeException('Too many setup attempts. Please wait a minute.');
}
$username = trim((string) ($_POST['admin_username'] ?? ''));
$password = (string) ($_POST['admin_password'] ?? '');
$passwordConfirm = (string) ($_POST['admin_password_confirm'] ?? '');
$apiToken = trim((string) ($_POST['api_token'] ?? ''));
$jsonUrl = trim((string) ($_POST['json_source_url'] ?? DEFAULT_JSON_SOURCE_URL));
$vpnName = trim((string) ($_POST['vpn_name'] ?? 'XAMBoost VPN'));
$vpnDescription = trim((string) ($_POST['vpn_description'] ?? 'Fast self-updating VPN subscription panel.'));
$accentColor = normalize_accent_color((string) ($_POST['accent_color'] ?? '#22c55e'));
$logoUrl = trim((string) ($_POST['logo_url'] ?? ''));
if ($username === '' || strlen($username) < 3) {
throw new RuntimeException('Admin username must be at least 3 characters.');
}
if ($password === '' || strlen($password) < 8) {
throw new RuntimeException('Admin password must be at least 8 characters.');
}
if (!hash_equals($password, $passwordConfirm)) {
throw new RuntimeException('Password confirmation does not match.');
}
if ($apiToken === '' || strlen($apiToken) < 16) {
throw new RuntimeException('API token must be at least 16 characters.');
}
if (!filter_var($jsonUrl, FILTER_VALIDATE_URL)) {
throw new RuntimeException('JSON source URL is invalid.');
}
$uploadedLogo = save_uploaded_logo();
if ($uploadedLogo !== null) {
$logoUrl = $uploadedLogo;
}
initialize_database();
set_settings([
'admin_username' => $username,
'admin_password_hash' => password_hash($password, PASSWORD_DEFAULT),
'api_token_hash' => password_hash($apiToken, PASSWORD_DEFAULT),
'json_source_url' => $jsonUrl,
'vpn_name' => $vpnName !== '' ? $vpnName : 'XAMBoost VPN',
'vpn_description' => $vpnDescription,
'logo_url' => $logoUrl,
'accent_color' => $accentColor,
'server_renames' => '{}',
]);
flash('success', 'Setup complete. Sign in with the admin account you just created.');
redirect('login');
} catch (Throwable $exception) {
$error = $exception->getMessage();
}
}
$defaults = [
'admin_username' => (string) ($_POST['admin_username'] ?? 'admin'),
'json_source_url' => (string) ($_POST['json_source_url'] ?? DEFAULT_JSON_SOURCE_URL),
'vpn_name' => (string) ($_POST['vpn_name'] ?? 'XAMBoost VPN'),
'vpn_description' => (string) ($_POST['vpn_description'] ?? 'Fast self-updating VPN subscription panel.'),
'accent_color' => (string) ($_POST['accent_color'] ?? '#22c55e'),
'logo_url' => (string) ($_POST['logo_url'] ?? ''),
];
render_shell_start('Setup Wizard', 'First launch setup for admin credentials, API access, branding, and the dynamic source URL.', false);
?>
<div class="grid gap-6 lg:grid-cols-[1.1fr_0.9fr]">
<section class="glass rounded-[28px] p-6 sm:p-8">
<div class="mb-6">
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-slate-500">Shared Hosting Ready</p>
<h2 class="mt-2 text-2xl font-extrabold text-slate-900">Configure the panel once</h2>
<p class="mt-2 text-sm text-slate-600">This wizard initializes SQLite, saves your branding, stores the API token hash, and keeps the source URL editable for later changes.</p>
</div>
<?php if ($error !== null): ?>
<div class="mb-5 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm font-medium text-rose-800"><?= html_escape($error) ?></div>
<?php endif; ?>
<form method="post" enctype="multipart/form-data" class="space-y-5">
<div class="grid gap-5 sm:grid-cols-2">
<?php render_form_input('Admin Username', 'admin_username', $defaults['admin_username'], 'text', true, 'admin'); ?>
<?php render_form_input('API Token', 'api_token', '', 'text', true, 'At least 16 characters'); ?>
</div>
<div class="grid gap-5 sm:grid-cols-2">
<?php render_form_input('Admin Password', 'admin_password', '', 'password', true); ?>
<?php render_form_input('Confirm Password', 'admin_password_confirm', '', 'password', true); ?>
</div>
<?php render_form_input('Source JSON URL', 'json_source_url', $defaults['json_source_url'], 'url', true); ?>
<div class="grid gap-5 sm:grid-cols-2">
<?php render_form_input('VPN Name', 'vpn_name', $defaults['vpn_name'], 'text', true); ?>
<?php render_form_input('Accent Color', 'accent_color', $defaults['accent_color'], 'text', true, '#22c55e'); ?>
</div>
<?php render_form_textarea('Brand Description', 'vpn_description', $defaults['vpn_description'], 4, 'What users should see on the subscription page'); ?>
<div class="grid gap-5 sm:grid-cols-2">
<?php render_form_input('Logo URL', 'logo_url', $defaults['logo_url'], 'url', false, 'https://example.com/logo.png'); ?>
<label class="block">
<span class="mb-2 block text-sm font-semibold text-slate-700">Upload Logo</span>
<input type="file" name="logo_file" accept=".png,.jpg,.jpeg,.webp,.gif" class="w-full rounded-2xl border border-white/60 bg-white/70 px-4 py-[0.85rem] text-sm text-slate-900 shadow-sm outline-none transition focus:border-slate-300 focus:ring-2 focus:ring-slate-200">
</label>
</div>
<button type="submit" class="btn-primary inline-flex items-center justify-center rounded-2xl px-5 py-3 text-sm font-semibold shadow-lg">Finish Setup</button>
</form>
</section>
<aside class="space-y-6">
<div class="glass rounded-[28px] p-6">
<h3 class="text-lg font-bold text-slate-900">What gets created</h3>
<ul class="mt-4 space-y-3 text-sm text-slate-600">
<li>SQLite schema for subscriptions, plans, and settings</li>
<li>Hashed admin password and hashed API token</li>
<li>Editable branding and default JSON source</li>
<li>Ready-to-use admin panel and token API routes</li>
</ul>
</div>
<div class="glass rounded-[28px] p-6">
<h3 class="text-lg font-bold text-slate-900">Default behavior</h3>
<p class="mt-3 text-sm text-slate-600">Servers are never stored in SQLite. The panel fetches the remote JSON on every request and reuses a 60-second file cache only for delivery speed and resilience.</p>
</div>
</aside>
</div>
<?php render_shell_end();