feature: Initial commit

This commit is contained in:
dev
2026-01-01 19:20:06 +00:00
commit ae491f1720
8 changed files with 1503 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
<?php
$dbPath = __DIR__ . '/../dev_panel.db';
function getDb() {
static $pdo = null;
global $dbPath;
if ($pdo === null) {
$pdo = new PDO('sqlite:' . $dbPath);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
return $pdo;
}
function initDb() {
$db = getDb();
$db->exec('
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
role TEXT NOT NULL CHECK (role IN ("admin","dev"))
);
');
$db->exec('
CREATE TABLE IF NOT EXISTS sites (
id INTEGER PRIMARY KEY AUTOINCREMENT,
domain TEXT NOT NULL UNIQUE,
owner_id INTEGER,
created_at TEXT NOT NULL,
FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE SET NULL
);
');
// Seed initial admin if no users exist
$count = (int) $db->query('SELECT COUNT(*) FROM users')->fetchColumn();
if ($count === 0) {
$hash = password_hash('change-me', PASSWORD_DEFAULT);
$stmt = $db->prepare('INSERT INTO users (username, password_hash, role) VALUES (?, ?, ?)');
$stmt->execute(['admin', $hash, 'admin']);
return ['adminCreated' => true, 'username' => 'admin', 'password' => 'change-me'];
}
return ['adminCreated' => false];
}

View File

@@ -0,0 +1,128 @@
<?php
require_once __DIR__ . '/db.php';
function getCurrentUser() {
if (!isset($_SESSION['user_id'])) {
return null;
}
return [
'id' => $_SESSION['user_id'],
'username' => $_SESSION['username'],
'role' => $_SESSION['role'],
];
}
function isAdmin() {
$user = getCurrentUser();
return $user && $user['role'] === 'admin';
}
function requireLogin($action) {
$publicActions = ['login'];
if (!in_array($action, $publicActions, true) && !isset($_SESSION['user_id'])) {
header('Location: ?action=login');
exit;
}
}
// ---------- Misc helpers ----------
function generatePassword($length = 16) {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
$pass = '';
$max = strlen($chars) - 1;
for ($i = 0; $i < $length; $i++) {
$pass .= $chars[random_int(0, $max)];
}
return $pass;
}
// ---------- CLI Helpers ----------
function stripAnsi($str) {
// Remove ANSI escape sequences like \e[94m ... \e[0m
return preg_replace('/\x1B\[[0-9;]*[A-Za-z]/', '', $str);
}
function runCommand($cmd, &$output = null, &$status = null) {
$output = [];
exec($cmd . ' 2>&1', $output, $status);
// Strip ANSI codes from each line
foreach ($output as &$line) {
$line = stripAnsi($line);
}
return $status === 0;
}
function runCommandStreaming($cmd, callable $onLine) {
$descriptorspec = [
0 => ['pipe', 'r'], // stdin
1 => ['pipe', 'w'], // stdout
2 => ['pipe', 'w'], // stderr
];
$process = proc_open($cmd . ' 2>&1', $descriptorspec, $pipes);
if (!is_resource($process)) {
return [false, ['Failed to start process']];
}
// We don't need stdin
fclose($pipes[0]);
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
$allLines = [];
while (true) {
$status = proc_get_status($process);
$out = fgets($pipes[1]);
$err = fgets($pipes[2]);
foreach ([$out, $err] as $chunk) {
if ($chunk !== false && $chunk !== '') {
$chunk = stripAnsi($chunk);
$lines = preg_split("/\r\n|\n|\r/", $chunk);
foreach ($lines as $line) {
if ($line === '') {
continue;
}
$allLines[] = $line;
$onLine($line);
}
@ob_flush();
@flush();
}
}
if (!$status['running']) {
break;
}
usleep(50000); // 50ms
}
fclose($pipes[1]);
fclose($pipes[2]);
$exitCode = proc_close($process);
return [$exitCode === 0, $allLines];
}
function sanitizeDomain($domain) {
$domain = trim($domain);
if ($domain === '') {
return null;
}
// Allow letters, numbers, dots, and dashes
if (!preg_match('/^[a-zA-Z0-9.-]+$/', $domain)) {
return null;
}
return $domain;
}