Release
This commit is contained in:
207
web/setup.php
Normal file
207
web/setup.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
require __DIR__ . '/lib/app.php';
|
||||
require __DIR__ . '/lib/snowctl.php';
|
||||
|
||||
if (function_exists('app_is_installed') && app_is_installed()) {
|
||||
header('Location: /'); exit;
|
||||
}
|
||||
|
||||
$err = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$u = trim($_POST['u'] ?? '');
|
||||
$p = $_POST['p'] ?? '';
|
||||
$p2 = $_POST['p2'] ?? '';
|
||||
|
||||
$broker = trim($_POST['broker'] ?? 'https://snowflake-broker.torproject.net/');
|
||||
$stun = trim($_POST['stun'] ?? 'stun:stun.l.google.com:19302');
|
||||
$range = trim($_POST['range'] ?? '10000:65535');
|
||||
$unsafe = isset($_POST['unsafe']);
|
||||
|
||||
$cap_gb = (int)($_POST['cap_gb'] ?? 100);
|
||||
$cap_reset_day= (int)($_POST['cap_reset_day']?? 1);
|
||||
$rate_mbps = (int)($_POST['rate_mbps'] ?? 0);
|
||||
|
||||
if ($u === '' || $p === '') {
|
||||
$err = 'Username and password are required.';
|
||||
} elseif ($p !== $p2) {
|
||||
$err = 'Passwords do not match.';
|
||||
} elseif ($cap_reset_day < 1 || $cap_reset_day > 28) {
|
||||
$err = 'Reset day must be between 1 and 28.';
|
||||
} else {
|
||||
$app_cfg = [
|
||||
'admin_user' => $u,
|
||||
'admin_pass' => password_hash($p, PASSWORD_DEFAULT),
|
||||
'created_at' => date('c'),
|
||||
'cap_gb' => max(0, $cap_gb),
|
||||
'cap_reset_day' => $cap_reset_day,
|
||||
'rate_mbps' => max(0, $rate_mbps),
|
||||
];
|
||||
if (function_exists('app_save_config')) {
|
||||
app_save_config($app_cfg);
|
||||
} else {
|
||||
$dir = '/etc/snowpanel';
|
||||
@mkdir($dir, 0770, true);
|
||||
@file_put_contents("$dir/app.json", json_encode($app_cfg, JSON_PRETTY_PRINT), LOCK_EX);
|
||||
@chgrp($dir, 'www-data'); @chmod($dir, 0770);
|
||||
@chgrp("$dir/app.json", 'www-data'); @chmod("$dir/app.json", 0660);
|
||||
}
|
||||
|
||||
$flags = snowctl_build_flags([
|
||||
'broker'=>$broker,
|
||||
'stun'=>$stun,
|
||||
'range'=>$range,
|
||||
'unsafe'=>$unsafe?1:0
|
||||
]);
|
||||
snowctl_apply_override($flags);
|
||||
|
||||
header('Location: /login.php?ok=1'); exit;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>First-time Setup · SnowPanel</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/assets/panel.css" rel="stylesheet">
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<link rel="alternate icon" href="/favicon.svg">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-dark" style="background:linear-gradient(90deg,#0d6efd,#4dabf7);">
|
||||
<div class="container-fluid">
|
||||
<span class="navbar-brand fw-bold">
|
||||
<img src="/favicon.svg" alt="" style="width:20px;height:20px;margin-right:.5rem;vertical-align:-3px;">
|
||||
SnowPanel
|
||||
</span>
|
||||
<div class="ms-auto">
|
||||
<button id="btnTheme" type="button" class="btn btn-sm btn-outline-light">Theme</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="page-wrap">
|
||||
<div class="container maxw-960">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
<div class="card p-4 shadow-lg">
|
||||
<h1 class="h4 mb-3">First-time Setup</h1>
|
||||
|
||||
<?php if ($err): ?>
|
||||
<div class="alert alert-danger py-2 mb-3" role="alert"><?= htmlspecialchars($err) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="/setup.php" autocomplete="on">
|
||||
<div class="mb-2"><div class="small text-muted">Step 1</div><div class="h5 mb-2">Admin account</div></div>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="u">Username</label>
|
||||
<input class="form-control" id="u" name="u" required autofocus value="<?= htmlspecialchars($_POST['u'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="p">Password</label>
|
||||
<input class="form-control" id="p" name="p" type="password" required autocomplete="new-password">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="p2">Confirm password</label>
|
||||
<input class="form-control" id="p2" name="p2" type="password" required autocomplete="new-password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 mb-2"><div class="small text-muted">Step 2</div><div class="h5 mb-2">Proxy basics</div></div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="broker">Broker URL</label>
|
||||
<input class="form-control" id="broker" name="broker" placeholder="https://snowflake-broker.torproject.net/"
|
||||
value="<?= htmlspecialchars($_POST['broker'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="stun">STUN server</label>
|
||||
<input class="form-control" id="stun" name="stun" placeholder="stun:stun.l.google.com:19302"
|
||||
value="<?= htmlspecialchars($_POST['stun'] ?? '') ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mt-1">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="range">Ephemeral ports range</label>
|
||||
<input class="form-control" id="range" name="range" placeholder="10000:65535"
|
||||
value="<?= htmlspecialchars($_POST['range'] ?? '10000:65535') ?>">
|
||||
</div>
|
||||
<div class="col-md-4 form-check mt-4">
|
||||
<input class="form-check-input" type="checkbox" id="unsafe" name="unsafe" <?= isset($_POST['unsafe']) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="unsafe">Unsafe verbose logging</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 mb-2"><div class="small text-muted">Step 3</div><div class="h5 mb-2">Limits</div></div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="cap_gb">Monthly cap</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" id="cap_gb" name="cap_gb" type="number" min="0" step="1" placeholder="100"
|
||||
value="<?= htmlspecialchars($_POST['cap_gb'] ?? '100') ?>">
|
||||
<span class="input-group-text">GB</span>
|
||||
</div>
|
||||
<div class="form-text">0 = unlimited</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="cap_reset_day">Reset day</label>
|
||||
<select id="cap_reset_day" name="cap_reset_day" class="form-select">
|
||||
<?php
|
||||
$sel = (int)($_POST['cap_reset_day'] ?? 1);
|
||||
for($d=1;$d<=28;$d++){
|
||||
$s = ($sel===$d)?' selected':'';
|
||||
echo "<option$s>$d</option>";
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="rate_mbps">Throughput limit</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" id="rate_mbps" name="rate_mbps" type="number" min="0" step="1" placeholder="0"
|
||||
value="<?= htmlspecialchars($_POST['rate_mbps'] ?? '0') ?>">
|
||||
<span class="input-group-text">Mbps</span>
|
||||
</div>
|
||||
<div class="form-text">0 = unlimited (display only)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2 mt-4">
|
||||
<button class="btn btn-primary" type="submit">Save & Finish</button>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning mt-3 py-2">
|
||||
<div class="fw-semibold">Reminder</div>
|
||||
<ul class="mb-0">
|
||||
<li>No port forwarding is required for Snowflake.</li>
|
||||
<li>Allow outbound UDP and the chosen ephemeral range.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const THEME_KEY='snowpanel:theme';
|
||||
const mql=window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const btn=document.getElementById('btnTheme');
|
||||
function preferredTheme(){const s=localStorage.getItem(THEME_KEY);return (s==='dark'||s==='light')?s:(mql.matches?'dark':'light');}
|
||||
function setBtnLabel(t){ if(btn) btn.textContent=(t==='dark')?'🌞 Light':'🌙 Dark'; }
|
||||
function applyTheme(t){ document.documentElement.setAttribute('data-theme', t); localStorage.setItem(THEME_KEY,t); setBtnLabel(t); }
|
||||
mql.addEventListener('change',e=>{ if(!localStorage.getItem(THEME_KEY)) applyTheme(e.matches?'dark':'light'); });
|
||||
btn?.addEventListener('click',()=>applyTheme(document.documentElement.getAttribute('data-theme')==='dark'?'light':'dark'));
|
||||
applyTheme(preferredTheme());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user