forked from TildeClub/site
Compare commits
No commits in common. "master" and "deepend-tildeclub-patch-2" have entirely different histories.
master
...
deepend-ti
|
@ -1,5 +1,2 @@
|
||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
ko_fi: tildeclub # Replace with a single Liberapay username
|
liberapay: tilde.club # Replace with a single Liberapay username
|
||||||
github: tildeclub
|
|
||||||
custom: https://www.paypal.com/donate?hosted_button_id=DWHSADKJ26HZ8
|
|
||||||
custom: https://donate.tilde.club
|
|
||||||
|
|
|
@ -12,4 +12,3 @@ news/
|
||||||
icons/
|
icons/
|
||||||
stats/
|
stats/
|
||||||
cache/
|
cache/
|
||||||
polls/polls.db
|
|
||||||
|
|
|
@ -5,13 +5,14 @@
|
||||||
<br><br>
|
<br><br>
|
||||||
<!-- Tildeverse Banner Exchange code begin -->
|
<!-- Tildeverse Banner Exchange code begin -->
|
||||||
<div style="text-align: center;">
|
<div style="text-align: center;">
|
||||||
<iframe src="https://tildeexchange.org/work.php?ID=deepend" width="468" height="60" marginwidth="0" marginheight="0" hspace="0" vspace="0" frameborder="0" scrolling="no"></iframe>
|
<iframe src="https://banner.tildeverse.org/work.php?ID=deepend" width="468" height="60" marginwidth="0" marginheight="0" hspace="0" vspace="0" frameborder="0" scrolling="no"></iframe>
|
||||||
<p><a href="https://tildeexchange.org" target="_blank">Tildeverse Banner Exchange</a></p>
|
<p><a href="https://banner.tildeverse.org" target="_blank">Tildeverse Banner Exchange</a></p>
|
||||||
</div>
|
</div>
|
||||||
<!-- Tildeverse Banner Exchange code end -->
|
<!-- Tildeverse Banner Exchange code end -->
|
||||||
<br><br>
|
<br><br>
|
||||||
<p>ECDSA fingerprint: SHA256:duamOATgnGcfRFFkotCwrAWzZtRjwxm64WAhq5tQRwE</p>
|
<p>ECDSA fingerprint: SHA256:duamOATgnGcfRFFkotCwrAWzZtRjwxm64WAhq5tQRwE</p>
|
||||||
<a href="https://github.com/tildeclub/site">page source</a> |
|
<a href="https://github.com/tildeclub/site">page source</a> |
|
||||||
|
<a href="http://updown.tilde.club/">Uptime Status</a> |
|
||||||
<a href="https://tilde.club/stats" target="_blank">Stats</a> |
|
<a href="https://tilde.club/stats" target="_blank">Stats</a> |
|
||||||
<a href="https://tilde.club/news.php">NNTP Stats</a>
|
<a href="https://tilde.club/news.php">NNTP Stats</a>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title><?=isset($title) ? $title : "Welcome to ~tilde.club~"?></title>
|
<title><?=isset($title) ? $title : "Welcome to ~tilde.club~"?></title>
|
||||||
<link rel="icon" type="image/png" href="https://tilde.club/favicon.png">
|
<link rel="icon" type="image/png" href="favicon.png">
|
||||||
|
|
||||||
<!-- Preload CSS and image resources -->
|
<!-- Preload CSS and image resources -->
|
||||||
<link rel="preload" href="/style.css" as="style">
|
<link rel="preload" href="/style.css" as="style">
|
||||||
|
|
|
@ -12,8 +12,7 @@
|
||||||
<a href="/">~TILDE.CLUB~</a>
|
<a href="/">~TILDE.CLUB~</a>
|
||||||
<a href="/">HOME</a>
|
<a href="/">HOME</a>
|
||||||
<a href="/wiki/">WIKI</a>
|
<a href="/wiki/">WIKI</a>
|
||||||
<a href="https://tildeforge.dev" target="_blank">TILDEFORGE</a>
|
<a href="https://tilde.club/wiki/donate.html">DONATE</a>
|
||||||
<a href="https://donate.tilde.club">DONATE</a>
|
|
||||||
<a href="https://web.newnet.net/?join=club" target="_blank">CHAT</a>
|
<a href="https://web.newnet.net/?join=club" target="_blank">CHAT</a>
|
||||||
<a href="/signup/">SIGN-UP</a>
|
<a href="/signup/">SIGN-UP</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
82
index.php
82
index.php
|
@ -11,7 +11,7 @@ if (isset($_GET['notice'])) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<h1 id="fancyboi">welcome to Tilde.club</h1>
|
<h1 id="fancyboi">welcome to tilde.club</h1>
|
||||||
|
|
||||||
<p><a href="/wiki/faq.html">Questions? See the official FAQ.</a></p>
|
<p><a href="/wiki/faq.html">Questions? See the official FAQ.</a></p>
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ if (isset($_GET['notice'])) {
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<div id="news" class="col">
|
<div class="col">
|
||||||
<?php
|
<?php
|
||||||
$news = json_decode(file_get_contents('news.json'), true);
|
$news = json_decode(file_get_contents('news.json'), true);
|
||||||
|
|
||||||
|
@ -80,14 +80,8 @@ if (isset($_GET['notice'])) {
|
||||||
|
|
||||||
// Display news items
|
// Display news items
|
||||||
foreach ($filteredNews as $newsItem) {
|
foreach ($filteredNews as $newsItem) {
|
||||||
echo '<div class="news-item">';
|
|
||||||
|
|
||||||
echo '<div class="news-item-title">';
|
|
||||||
echo '<h2>' . htmlspecialchars($newsItem['title']) . ':</h2>';
|
echo '<h2>' . htmlspecialchars($newsItem['title']) . ':</h2>';
|
||||||
echo '<h3>' . htmlspecialchars($newsItem['heading']) . '</h3>';
|
echo '<h3>' . htmlspecialchars($newsItem['heading']) . '</h3>';
|
||||||
echo '</div>';
|
|
||||||
|
|
||||||
echo '<div class="news-item-body">';
|
|
||||||
echo '<p>' . htmlspecialchars($newsItem['content']) . '</p>';
|
echo '<p>' . htmlspecialchars($newsItem['content']) . '</p>';
|
||||||
|
|
||||||
if (isset($newsItem['details']) && is_array($newsItem['details'])) {
|
if (isset($newsItem['details']) && is_array($newsItem['details'])) {
|
||||||
|
@ -105,9 +99,7 @@ if (isset($_GET['notice'])) {
|
||||||
if (isset($newsItem['additional_content'])) {
|
if (isset($newsItem['additional_content'])) {
|
||||||
echo '<p>' . htmlspecialchars($newsItem['additional_content']) . '</p>';
|
echo '<p>' . htmlspecialchars($newsItem['additional_content']) . '</p>';
|
||||||
}
|
}
|
||||||
echo '</div>';
|
|
||||||
|
|
||||||
echo '</div>';
|
|
||||||
echo '<hr>';
|
echo '<hr>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,10 +119,9 @@ if (isset($_GET['notice'])) {
|
||||||
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<p>
|
<p>
|
||||||
Tilde.club gives you access to a shared Unix computer
|
tilde.club is not a social network it is one tiny totally
|
||||||
where you can make web pages, learn, and share knowledge.
|
standard unix computer that people respectfully use together
|
||||||
It's a place where working together happens naturally,
|
in their shared quest to build awesome web pages
|
||||||
all within a friendly and supportive setting.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
@ -138,12 +129,12 @@ if (isset($_GET['notice'])) {
|
||||||
RECENTLY CHANGED PAGES</a> you can see that too
|
RECENTLY CHANGED PAGES</a> you can see that too
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Or Check out the <a href="https://tilde.club/~tweska/gallery" target="blank">Tilde.club gallery</a> created by <a href="/~tweska" target="_blank">~tweska</a>
|
Or Check out the <a href="https://tilde.club/~tweska/gallery" target="blank">tilde.club gallery</a> created by <a href="/~tweska" target="_blank">~tweska</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h2>Tilde.club gold star supporters</h2>
|
<h2>tilde.club gold star supporters</h2>
|
||||||
<p>Tilde.club is supported by a global community of
|
<p>Tilde.Club is supported by a global community of
|
||||||
good people. We don't rank people by the amount
|
good people. We don't rank people by the amount
|
||||||
they give, only by the fact that they gave.
|
they give, only by the fact that they gave.
|
||||||
Here's who has donated! When you're on the
|
Here's who has donated! When you're on the
|
||||||
|
@ -174,11 +165,11 @@ if (isset($_GET['notice'])) {
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h3>Here are the home pages of our users</h3>
|
<h3>here are the home pages of our users</h3>
|
||||||
<p>This list does not include people who haven't changed their page yet.</p>
|
<p>this list does not include people who haven't changed their page yet.</p>
|
||||||
<p>If you're not seeing yourself listed here, change your page from the default.</p>
|
<p>if you're not seeing yourself listed here, change your page from the default.</p>
|
||||||
<p>Users with recently updated pages are highlighted in a lighter color.</p>
|
<p>users with recently updated pages within the last month are highlighted in a lighter color.</p>
|
||||||
<p><a href="/users/">List all users</a></p>
|
<p><a href="/users/">list all users</a></p>
|
||||||
<?php
|
<?php
|
||||||
// these are the hashes of previous and current default pages
|
// these are the hashes of previous and current default pages
|
||||||
$page_shas = [
|
$page_shas = [
|
||||||
|
@ -209,62 +200,49 @@ if (isset($_GET['notice'])) {
|
||||||
"b51a889545b5f065fd1ac2b8760cab0088a9dc22"
|
"b51a889545b5f065fd1ac2b8760cab0088a9dc22"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$oneMonthAgo = strtotime('-1 month');
|
||||||
|
|
||||||
// Retrieve from cache file if available
|
// Retrieve from cache file if available
|
||||||
$cache_file = 'cache/homepages_list.html';
|
$cache_file = 'cache/homepages_list.html';
|
||||||
|
|
||||||
if (file_exists($cache_file) and time() - filemtime($cache_file) < 86400)
|
if (file_exists($cache_file) and time() - filemtime($cache_file) < 86400)
|
||||||
{
|
{
|
||||||
$homepagesOutput = file_get_contents($cache_file);
|
$homepages_list = file_get_contents($cache_file);
|
||||||
}
|
}
|
||||||
|
// Cache not available or expired
|
||||||
// Cache not available or expired - create list
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$homepagesOutput = '<div class="user-list">';
|
$homepages_list = '<div class="user-list">';
|
||||||
$now = time();
|
|
||||||
|
|
||||||
foreach (glob("/home/*") as $user)
|
foreach (glob("/home/*") as $user) {
|
||||||
{
|
|
||||||
// Look for index files with common extensions
|
// Look for index files with common extensions
|
||||||
$indexFiles = glob("$user/public_html/index.{html,htm,php}", GLOB_BRACE);
|
$indexFiles = glob("$user/public_html/index.{html,htm,php}", GLOB_BRACE);
|
||||||
$index = count($indexFiles) > 0 ? $indexFiles[0] : null;
|
$index = count($indexFiles) > 0 ? $indexFiles[0] : null;
|
||||||
|
|
||||||
if (!$index || in_array(sha1_file($index), $page_shas)) continue;
|
if (!$index || in_array(sha1_file($index), $page_shas)) continue;
|
||||||
|
|
||||||
// determine the most recently updated file
|
// Check if the index pages were updated in the last month
|
||||||
$age = 0;
|
$recentChange = false;
|
||||||
|
|
||||||
foreach ($indexFiles as $file)
|
foreach ($indexFiles as $file) {
|
||||||
{
|
if (filemtime($file) > $oneMonthAgo) {
|
||||||
$access = filemtime($file);
|
$recentChange = true;
|
||||||
|
break;
|
||||||
if ($access > $age)
|
}
|
||||||
$age = $access;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = basename($user);
|
$user = basename($user);
|
||||||
|
|
||||||
// For simplicity, we use a maximum of 50 months old
|
|
||||||
$monthsOld = floor(($now - $age) / 2592000);
|
|
||||||
if ($monthsOld > 50) $monthsOld = 50;
|
|
||||||
|
|
||||||
// Set opacity in steps of 5
|
$homepages_list .= '<a href="/~'.$user.'/">'.(($recentChange) ? '<b>~'.$user.'</b>' : '~'.$user).'</a>';
|
||||||
$opacity = 100 - 2 * $monthsOld;
|
|
||||||
$opacity = ceil($opacity / 5) * 5;
|
|
||||||
|
|
||||||
// Minimum is 15% opacity
|
|
||||||
if ($opacity < 15) $opacity = 15;
|
|
||||||
|
|
||||||
$homepagesOutput .= '<a data-op="'.$opacity.'" href="/~'.$user.'/">'.$user.'</a>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$homepagesOutput .= '</div>';
|
$homepages_list .= '</div>';
|
||||||
|
|
||||||
// Save cache file
|
// Save cache file
|
||||||
$save_cache = file_put_contents($cache_file, $homepagesOutput);
|
$save_cache = file_put_contents($cache_file, $homepages_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
echo $homepagesOutput;
|
echo $homepages_list;
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
3
nav.html
3
nav.html
|
@ -2,8 +2,7 @@
|
||||||
<a href="/">~TILDE.CLUB~</a>
|
<a href="/">~TILDE.CLUB~</a>
|
||||||
<a href="/">HOME</a>
|
<a href="/">HOME</a>
|
||||||
<a href="/wiki/">WIKI</a>
|
<a href="/wiki/">WIKI</a>
|
||||||
<a href="https://tildeforge.dev" target="_blank">TILDEFORGE</a>
|
<a href="https://tilde.club/wiki/donate.html">DONATE</a>
|
||||||
<a href="https://donate.tilde.club">DONATE</a>
|
|
||||||
<a href="https://web.newnet.net/?join=club" target="_blank">CHAT</a>
|
<a href="https://web.newnet.net/?join=club" target="_blank">CHAT</a>
|
||||||
<a href="/signup/">SIGN-UP</a>
|
<a href="/signup/">SIGN-UP</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
478
polls/admin.php
478
polls/admin.php
|
@ -1,347 +1,161 @@
|
||||||
<?php
|
<?php
|
||||||
session_start();
|
header('Content-Type: application/json');
|
||||||
require_once 'db.php'; // Ensure the database and $db PDO instance are available
|
require_once 'db.php';
|
||||||
|
|
||||||
// ------------------------------
|
// Quick helper to send JSON responses and exit
|
||||||
// Utility Function: Check if admin is logged in
|
function sendJson($data, $statusCode = 200) {
|
||||||
// ------------------------------
|
http_response_code($statusCode);
|
||||||
function isAdminLoggedIn()
|
echo json_encode($data);
|
||||||
{
|
|
||||||
return isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'] === true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------
|
|
||||||
// Handle Admin Login
|
|
||||||
// ------------------------------
|
|
||||||
if (isset($_POST['login'])) {
|
|
||||||
$username = $_POST['username'] ?? '';
|
|
||||||
$password = $_POST['password'] ?? '';
|
|
||||||
|
|
||||||
// Prepare a query to fetch the user
|
|
||||||
$stmt = $db->prepare("SELECT * FROM users WHERE username = :username LIMIT 1");
|
|
||||||
$stmt->bindValue(':username', $username, PDO::PARAM_STR);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if ($user && password_verify($password, $user['password'])) {
|
|
||||||
// Successful login
|
|
||||||
$_SESSION['admin_logged_in'] = true;
|
|
||||||
header('Location: admin.php');
|
|
||||||
exit;
|
|
||||||
} else {
|
|
||||||
$error = "Invalid credentials. Please try again.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------
|
|
||||||
// Handle Admin Logout
|
|
||||||
// ------------------------------
|
|
||||||
if (isset($_GET['action']) && $_GET['action'] === 'logout') {
|
|
||||||
session_destroy();
|
|
||||||
header('Location: admin.php');
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------
|
$action = $_GET['action'] ?? ($_POST['action'] ?? null);
|
||||||
// Handle Creating a New Poll
|
|
||||||
// ------------------------------
|
|
||||||
if (isset($_POST['create_poll']) && isAdminLoggedIn()) {
|
|
||||||
$questionText = trim($_POST['question_text'] ?? '');
|
|
||||||
|
|
||||||
if (!empty($questionText)) {
|
switch ($action) {
|
||||||
$stmt = $db->prepare("INSERT INTO poll_questions (question_text) VALUES (:question_text)");
|
// --------------------------------------------------
|
||||||
$stmt->bindValue(':question_text', $questionText, PDO::PARAM_STR);
|
// 1) List all polls (IDs + questions)
|
||||||
$stmt->execute();
|
// --------------------------------------------------
|
||||||
$successMsg = "Poll question created successfully!";
|
case 'list_polls':
|
||||||
} else {
|
try {
|
||||||
$errorMsg = "Please enter a question text.";
|
$stmt = $db->query("SELECT id, question_text FROM poll_questions ORDER BY id DESC");
|
||||||
}
|
$polls = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
}
|
sendJson(['success' => true, 'polls' => $polls]);
|
||||||
|
} catch (Exception $e) {
|
||||||
// ------------------------------
|
sendJson(['success' => false, 'error' => $e->getMessage()], 500);
|
||||||
// Handle Adding Options to an Existing Poll
|
|
||||||
// ------------------------------
|
|
||||||
if (isset($_POST['add_option']) && isAdminLoggedIn()) {
|
|
||||||
$questionId = (int)($_POST['poll_id'] ?? 0);
|
|
||||||
$optionText = trim($_POST['option_text'] ?? '');
|
|
||||||
|
|
||||||
if ($questionId > 0 && !empty($optionText)) {
|
|
||||||
// Check if poll question exists
|
|
||||||
$stmt = $db->prepare("SELECT id FROM poll_questions WHERE id = :id");
|
|
||||||
$stmt->bindValue(':id', $questionId, PDO::PARAM_INT);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
if ($stmt->fetchColumn()) {
|
|
||||||
// Insert the new option
|
|
||||||
$insertOption = $db->prepare("
|
|
||||||
INSERT INTO poll_options (question_id, option_text)
|
|
||||||
VALUES (:question_id, :option_text)
|
|
||||||
");
|
|
||||||
$insertOption->bindValue(':question_id', $questionId, PDO::PARAM_INT);
|
|
||||||
$insertOption->bindValue(':option_text', $optionText, PDO::PARAM_STR);
|
|
||||||
$insertOption->execute();
|
|
||||||
|
|
||||||
// Also initialize poll_results with a 0 vote count for the new option
|
|
||||||
$optionId = $db->lastInsertId();
|
|
||||||
$insertResult = $db->prepare("
|
|
||||||
INSERT INTO poll_results (question_id, option_id, vote_count)
|
|
||||||
VALUES (:question_id, :option_id, 0)
|
|
||||||
");
|
|
||||||
$insertResult->bindValue(':question_id', $questionId, PDO::PARAM_INT);
|
|
||||||
$insertResult->bindValue(':option_id', $optionId, PDO::PARAM_INT);
|
|
||||||
$insertResult->execute();
|
|
||||||
|
|
||||||
$successMsg = "Option added successfully!";
|
|
||||||
} else {
|
|
||||||
$errorMsg = "Poll question does not exist.";
|
|
||||||
}
|
}
|
||||||
} else {
|
break;
|
||||||
$errorMsg = "Please select a poll and enter an option text.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------
|
// --------------------------------------------------
|
||||||
// Handle Editing an Existing Poll
|
// 2) Get a single poll (question + options)
|
||||||
// ------------------------------
|
// --------------------------------------------------
|
||||||
if (isset($_POST['edit_poll']) && isAdminLoggedIn()) {
|
case 'get_poll':
|
||||||
$pollId = (int)($_POST['poll_id'] ?? 0);
|
$pollId = (int)($_GET['poll_id'] ?? 0);
|
||||||
$newQuestionText = trim($_POST['edit_question_text'] ?? '');
|
if ($pollId <= 0) {
|
||||||
|
sendJson(['success' => false, 'error' => 'Invalid poll_id'], 400);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Fetch poll question
|
||||||
|
$stmt = $db->prepare("SELECT id, question_text FROM poll_questions WHERE id = :id");
|
||||||
|
$stmt->execute([':id' => $pollId]);
|
||||||
|
$poll = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if ($pollId > 0 && !empty($newQuestionText)) {
|
if (!$poll) {
|
||||||
// Check if poll question exists
|
sendJson(['success' => false, 'error' => 'Poll not found'], 404);
|
||||||
$checkStmt = $db->prepare("SELECT id FROM poll_questions WHERE id = :id");
|
}
|
||||||
$checkStmt->bindValue(':id', $pollId, PDO::PARAM_INT);
|
|
||||||
$checkStmt->execute();
|
|
||||||
|
|
||||||
if ($checkStmt->fetchColumn()) {
|
// Fetch options
|
||||||
// Update the poll question
|
$optionsStmt = $db->prepare("
|
||||||
|
SELECT po.id AS option_id, po.option_text,
|
||||||
|
IFNULL(pr.vote_count, 0) AS vote_count
|
||||||
|
FROM poll_options po
|
||||||
|
LEFT JOIN poll_results pr ON po.id = pr.option_id
|
||||||
|
WHERE po.question_id = :question_id
|
||||||
|
ORDER BY po.id ASC
|
||||||
|
");
|
||||||
|
$optionsStmt->execute([':question_id' => $pollId]);
|
||||||
|
$options = $optionsStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
sendJson([
|
||||||
|
'success' => true,
|
||||||
|
'poll' => [
|
||||||
|
'id' => $poll['id'],
|
||||||
|
'question_text' => $poll['question_text'],
|
||||||
|
'options' => $options
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
sendJson(['success' => false, 'error' => $e->getMessage()], 500);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// 3) Cast a vote
|
||||||
|
// Expects: poll_id, option_id, username
|
||||||
|
// --------------------------------------------------
|
||||||
|
case 'vote':
|
||||||
|
// This can come from POST or GET. We'll assume POST for clarity.
|
||||||
|
$pollId = (int)($_POST['poll_id'] ?? 0);
|
||||||
|
$optionId = (int)($_POST['option_id'] ?? 0);
|
||||||
|
$username = trim($_POST['username'] ?? '');
|
||||||
|
|
||||||
|
if ($pollId <= 0 || $optionId <= 0 || empty($username)) {
|
||||||
|
sendJson(['success' => false, 'error' => 'Missing or invalid parameters'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user already voted on this poll
|
||||||
|
try {
|
||||||
|
// 1) Ensure poll & option exist
|
||||||
|
$checkOption = $db->prepare("
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM poll_options
|
||||||
|
WHERE id = :option_id
|
||||||
|
AND question_id = :poll_id
|
||||||
|
");
|
||||||
|
$checkOption->execute([
|
||||||
|
':option_id' => $optionId,
|
||||||
|
':poll_id' => $pollId
|
||||||
|
]);
|
||||||
|
if (!$checkOption->fetchColumn()) {
|
||||||
|
sendJson(['success' => false, 'error' => 'Option does not belong to poll or does not exist'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Check if user already voted
|
||||||
|
$checkVote = $db->prepare("
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM user_votes
|
||||||
|
WHERE question_id = :poll_id
|
||||||
|
AND user_name = :username
|
||||||
|
");
|
||||||
|
$checkVote->execute([
|
||||||
|
':poll_id' => $pollId,
|
||||||
|
':username' => $username
|
||||||
|
]);
|
||||||
|
if ($checkVote->fetchColumn() > 0) {
|
||||||
|
// Already voted
|
||||||
|
sendJson(['success' => false, 'error' => 'Already voted'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Cast the vote (increment poll_results)
|
||||||
$updateStmt = $db->prepare("
|
$updateStmt = $db->prepare("
|
||||||
UPDATE poll_questions
|
UPDATE poll_results
|
||||||
SET question_text = :question_text
|
SET vote_count = vote_count + 1
|
||||||
WHERE id = :id
|
WHERE question_id = :poll_id
|
||||||
|
AND option_id = :option_id
|
||||||
");
|
");
|
||||||
$updateStmt->bindValue(':question_text', $newQuestionText, PDO::PARAM_STR);
|
$updateStmt->execute([
|
||||||
$updateStmt->bindValue(':id', $pollId, PDO::PARAM_INT);
|
':poll_id' => $pollId,
|
||||||
$updateStmt->execute();
|
':option_id' => $optionId
|
||||||
|
]);
|
||||||
|
|
||||||
$successMsg = "Poll question updated successfully!";
|
// 4) Record the user vote
|
||||||
} else {
|
// Ensure user_votes table is created:
|
||||||
$errorMsg = "Poll question does not exist.";
|
// CREATE TABLE IF NOT EXISTS user_votes (
|
||||||
|
// id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
// question_id INTEGER NOT NULL,
|
||||||
|
// option_id INTEGER NOT NULL,
|
||||||
|
// user_name TEXT NOT NULL,
|
||||||
|
// voted_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
// );
|
||||||
|
$insertVote = $db->prepare("
|
||||||
|
INSERT INTO user_votes (question_id, option_id, user_name)
|
||||||
|
VALUES (:poll_id, :option_id, :username)
|
||||||
|
");
|
||||||
|
$insertVote->execute([
|
||||||
|
':poll_id' => $pollId,
|
||||||
|
':option_id' => $optionId,
|
||||||
|
':username' => $username
|
||||||
|
]);
|
||||||
|
|
||||||
|
sendJson(['success' => true, 'message' => 'Vote cast successfully']);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
sendJson(['success' => false, 'error' => $e->getMessage()], 500);
|
||||||
}
|
}
|
||||||
} else {
|
break;
|
||||||
$errorMsg = "Invalid poll ID or question text.";
|
|
||||||
}
|
// --------------------------------------------------
|
||||||
|
// 4) Unknown / default
|
||||||
|
// --------------------------------------------------
|
||||||
|
default:
|
||||||
|
sendJson(['success' => false, 'error' => 'Unknown action'], 400);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------
|
|
||||||
// Handle Deleting an Existing Poll
|
|
||||||
// ------------------------------
|
|
||||||
if (isset($_POST['delete_poll']) && isAdminLoggedIn()) {
|
|
||||||
$pollId = (int)($_POST['poll_id'] ?? 0);
|
|
||||||
|
|
||||||
if ($pollId > 0) {
|
|
||||||
// Check if poll question exists
|
|
||||||
$checkStmt = $db->prepare("SELECT id FROM poll_questions WHERE id = :id");
|
|
||||||
$checkStmt->bindValue(':id', $pollId, PDO::PARAM_INT);
|
|
||||||
$checkStmt->execute();
|
|
||||||
|
|
||||||
if ($checkStmt->fetchColumn()) {
|
|
||||||
// Delete poll_results
|
|
||||||
$deleteResults = $db->prepare("DELETE FROM poll_results WHERE question_id = :id");
|
|
||||||
$deleteResults->bindValue(':id', $pollId, PDO::PARAM_INT);
|
|
||||||
$deleteResults->execute();
|
|
||||||
|
|
||||||
// Delete poll_options
|
|
||||||
$deleteOptions = $db->prepare("DELETE FROM poll_options WHERE question_id = :id");
|
|
||||||
$deleteOptions->bindValue(':id', $pollId, PDO::PARAM_INT);
|
|
||||||
$deleteOptions->execute();
|
|
||||||
|
|
||||||
// Finally, delete the poll question
|
|
||||||
$deletePoll = $db->prepare("DELETE FROM poll_questions WHERE id = :id");
|
|
||||||
$deletePoll->bindValue(':id', $pollId, PDO::PARAM_INT);
|
|
||||||
$deletePoll->execute();
|
|
||||||
|
|
||||||
$successMsg = "Poll deleted successfully!";
|
|
||||||
} else {
|
|
||||||
$errorMsg = "Poll question does not exist.";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$errorMsg = "Invalid poll ID.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------
|
|
||||||
// Fetch All Polls for Display
|
|
||||||
// ------------------------------
|
|
||||||
$polls = [];
|
|
||||||
if (isAdminLoggedIn()) {
|
|
||||||
$pollsQuery = $db->query("SELECT id, question_text, created_at FROM poll_questions ORDER BY id DESC");
|
|
||||||
$polls = $pollsQuery->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Poll Admin</title>
|
|
||||||
<style>
|
|
||||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
|
||||||
.login-box, .admin-content { max-width: 600px; margin: 0 auto; }
|
|
||||||
.error { color: red; }
|
|
||||||
.success { color: green; }
|
|
||||||
h2 { border-bottom: 1px solid #ccc; }
|
|
||||||
form { margin-bottom: 20px; }
|
|
||||||
label { display: inline-block; width: 100px; }
|
|
||||||
input[type=text], input[type=password] { width: 200px; }
|
|
||||||
.poll-item { border: 1px solid #ccc; padding: 10px; margin-bottom: 20px; }
|
|
||||||
.poll-options { margin-top: 10px; }
|
|
||||||
.option-result { margin-left: 20px; }
|
|
||||||
.inline-form { display: inline-block; margin-right: 10px; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<?php if (!isAdminLoggedIn()): ?>
|
|
||||||
<div class="login-box">
|
|
||||||
<h2>Admin Login</h2>
|
|
||||||
|
|
||||||
<?php if (!empty($error)): ?>
|
|
||||||
<div class="error"><?php echo htmlspecialchars($error); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<form method="post" action="admin.php">
|
|
||||||
<div>
|
|
||||||
<label for="username">Username:</label>
|
|
||||||
<input type="text" name="username" id="username" required />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="password">Password:</label>
|
|
||||||
<input type="password" name="password" id="password" required />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button type="submit" name="login">Login</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="admin-content">
|
|
||||||
<h2>Poll Administration</h2>
|
|
||||||
<p>
|
|
||||||
<a href="admin.php?action=logout">Logout</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- Display success or error messages -->
|
|
||||||
<?php if (!empty($successMsg)): ?>
|
|
||||||
<div class="success"><?php echo htmlspecialchars($successMsg); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (!empty($errorMsg)): ?>
|
|
||||||
<div class="error"><?php echo htmlspecialchars($errorMsg); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<!-- Section: Create a New Poll -->
|
|
||||||
<h3>Create a New Poll</h3>
|
|
||||||
<form method="post" action="admin.php">
|
|
||||||
<div>
|
|
||||||
<label for="question_text">Question:</label>
|
|
||||||
<input type="text" name="question_text" id="question_text" required>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button type="submit" name="create_poll">Create Poll</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- Section: Add Options to Existing Poll -->
|
|
||||||
<h3>Add Options to a Poll</h3>
|
|
||||||
<?php if (count($polls) > 0): ?>
|
|
||||||
<form method="post" action="admin.php">
|
|
||||||
<div>
|
|
||||||
<label for="poll_id">Select Poll:</label>
|
|
||||||
<select name="poll_id" id="poll_id">
|
|
||||||
<?php foreach ($polls as $poll): ?>
|
|
||||||
<option value="<?php echo $poll['id']; ?>">
|
|
||||||
<?php echo htmlspecialchars($poll['question_text']); ?>
|
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="option_text">Option:</label>
|
|
||||||
<input type="text" name="option_text" id="option_text" required>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button type="submit" name="add_option">Add Option</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<?php else: ?>
|
|
||||||
<p>No polls available. Create a new poll first.</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<!-- Section: Existing Polls & Results -->
|
|
||||||
<h3>Existing Polls & Results</h3>
|
|
||||||
<?php if (count($polls) > 0): ?>
|
|
||||||
<?php foreach ($polls as $poll): ?>
|
|
||||||
<div class="poll-item">
|
|
||||||
<strong>Question:</strong>
|
|
||||||
<?php echo htmlspecialchars($poll['question_text']); ?><br>
|
|
||||||
<em>Created at: <?php echo $poll['created_at']; ?></em>
|
|
||||||
|
|
||||||
<!-- Edit and Delete forms for the poll -->
|
|
||||||
<div style="margin-top: 10px;">
|
|
||||||
<!-- Edit Form (inline) -->
|
|
||||||
<form method="post" class="inline-form">
|
|
||||||
<input type="hidden" name="poll_id" value="<?php echo $poll['id']; ?>">
|
|
||||||
<input type="text" name="edit_question_text" value="<?php echo htmlspecialchars($poll['question_text']); ?>" style="width:250px;">
|
|
||||||
<button type="submit" name="edit_poll">Save</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- Delete Form (inline) -->
|
|
||||||
<form method="post" class="inline-form" onsubmit="return confirm('Are you sure you want to delete this poll?');">
|
|
||||||
<input type="hidden" name="poll_id" value="<?php echo $poll['id']; ?>">
|
|
||||||
<button type="submit" name="delete_poll">Delete</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Display poll options and vote counts -->
|
|
||||||
<?php
|
|
||||||
// Fetch options
|
|
||||||
$optionsStmt = $db->prepare("
|
|
||||||
SELECT po.id as option_id, po.option_text,
|
|
||||||
pr.vote_count
|
|
||||||
FROM poll_options po
|
|
||||||
LEFT JOIN poll_results pr
|
|
||||||
ON po.id = pr.option_id
|
|
||||||
WHERE po.question_id = :question_id
|
|
||||||
ORDER BY po.id ASC
|
|
||||||
");
|
|
||||||
$optionsStmt->bindValue(':question_id', $poll['id'], PDO::PARAM_INT);
|
|
||||||
$optionsStmt->execute();
|
|
||||||
$options = $optionsStmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
?>
|
|
||||||
<div class="poll-options">
|
|
||||||
<?php if (!empty($options)): ?>
|
|
||||||
<ul>
|
|
||||||
<?php foreach ($options as $opt): ?>
|
|
||||||
<li>
|
|
||||||
<?php echo htmlspecialchars($opt['option_text']); ?>
|
|
||||||
<span class="option-result">
|
|
||||||
(Votes: <?php echo $opt['vote_count']; ?>)
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</ul>
|
|
||||||
<?php else: ?>
|
|
||||||
<p>No options for this poll yet.</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php else: ?>
|
|
||||||
<p>No polls to display.</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
230
polls/db.php
230
polls/db.php
|
@ -1,90 +1,166 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
session_start();
|
||||||
* db.php
|
|
||||||
*
|
|
||||||
* This file checks if the SQLite database file 'poll.db' exists.
|
|
||||||
* If not, it creates one and sets up the required tables for:
|
|
||||||
* - Users (including an admin user/password)
|
|
||||||
* - Poll questions
|
|
||||||
* - Poll options
|
|
||||||
* - Poll results
|
|
||||||
*/
|
|
||||||
|
|
||||||
$databaseFile = __DIR__ . '/poll.db';
|
// Include the database setup/connection
|
||||||
|
require_once 'db.php';
|
||||||
|
|
||||||
try {
|
// Initialize variables
|
||||||
// If the database file does not exist, create it and set it up
|
$error = '';
|
||||||
$dbExists = file_exists($databaseFile);
|
$success = '';
|
||||||
|
|
||||||
// Initialize the PDO connection
|
// Count how many users already exist in the database
|
||||||
$db = new PDO('sqlite:' . $databaseFile);
|
$checkTotal = $db->query("SELECT COUNT(*) FROM users")->fetchColumn();
|
||||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
||||||
|
|
||||||
// If the DB didn't exist before, create the required tables
|
// If at least one user exists, show a message and no form
|
||||||
if (!$dbExists) {
|
if ($checkTotal > 0) {
|
||||||
// Create 'users' table
|
?>
|
||||||
$db->exec("
|
<!DOCTYPE html>
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
<html>
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
<head>
|
||||||
username TEXT UNIQUE NOT NULL,
|
<meta charset="UTF-8">
|
||||||
password TEXT NOT NULL
|
<title>Setup Admin User</title>
|
||||||
);
|
<style>
|
||||||
");
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: blue;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h2>Admin User Already Exists</h2>
|
||||||
|
<p class="info">
|
||||||
|
An admin user has already been created. No additional admins can be set up here.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Go back to the <a href="index.php">Polls site</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<?php
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
// Create 'poll_questions' table
|
// If we are here, no user exists yet, so show the form
|
||||||
$db->exec("
|
if (isset($_POST['setup'])) {
|
||||||
CREATE TABLE IF NOT EXISTS poll_questions (
|
$username = trim($_POST['username'] ?? '');
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
$password = trim($_POST['password'] ?? '');
|
||||||
question_text TEXT NOT NULL,
|
$confirmPassword = trim($_POST['confirm_password'] ?? '');
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
");
|
|
||||||
|
|
||||||
// Create 'poll_options' table
|
// Basic validation
|
||||||
$db->exec("
|
if ($username === '' || $password === '' || $confirmPassword === '') {
|
||||||
CREATE TABLE IF NOT EXISTS poll_options (
|
$error = 'All fields are required.';
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
} elseif ($password !== $confirmPassword) {
|
||||||
question_id INTEGER NOT NULL,
|
$error = 'Passwords do not match.';
|
||||||
option_text TEXT NOT NULL,
|
} else {
|
||||||
FOREIGN KEY (question_id) REFERENCES poll_questions(id)
|
// Create the first (and only) admin user
|
||||||
);
|
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||||
");
|
$insertStmt = $db->prepare("
|
||||||
|
|
||||||
// Create 'poll_results' table
|
|
||||||
$db->exec("
|
|
||||||
CREATE TABLE IF NOT EXISTS poll_results (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
question_id INTEGER NOT NULL,
|
|
||||||
option_id INTEGER NOT NULL,
|
|
||||||
vote_count INTEGER NOT NULL DEFAULT 0,
|
|
||||||
FOREIGN KEY (question_id) REFERENCES poll_questions(id),
|
|
||||||
FOREIGN KEY (option_id) REFERENCES poll_options(id)
|
|
||||||
);
|
|
||||||
");
|
|
||||||
|
|
||||||
// Create a default admin user with a hashed password
|
|
||||||
// NOTE: In production, you should not hardcode these credentials.
|
|
||||||
// Instead, store them outside of your code or set them up once.
|
|
||||||
$adminUsername = 'admin';
|
|
||||||
$adminPlainPassword = 'password'; // Change this in production
|
|
||||||
$adminHashedPassword = password_hash($adminPlainPassword, PASSWORD_DEFAULT);
|
|
||||||
|
|
||||||
$insertUser = $db->prepare("
|
|
||||||
INSERT INTO users (username, password)
|
INSERT INTO users (username, password)
|
||||||
VALUES (:username, :password)
|
VALUES (:username, :password)
|
||||||
");
|
");
|
||||||
$insertUser->bindValue(':username', $adminUsername, PDO::PARAM_STR);
|
$insertStmt->bindValue(':username', $username, PDO::PARAM_STR);
|
||||||
$insertUser->bindValue(':password', $adminHashedPassword, PDO::PARAM_STR);
|
$insertStmt->bindValue(':password', $hashedPassword, PDO::PARAM_STR);
|
||||||
$insertUser->execute();
|
$insertStmt->execute();
|
||||||
}
|
|
||||||
|
|
||||||
// Optionally, you can return $db or leave it globally accessible
|
$success = "Admin user '$username' created successfully.";
|
||||||
// for other parts of your application.
|
}
|
||||||
// Example:
|
|
||||||
// return $db;
|
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
echo "Database error: " . $e->getMessage();
|
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Setup Admin User</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.success {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
input[type=text],
|
||||||
|
input[type=password] {
|
||||||
|
width: 250px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h2>Setup Admin User</h2>
|
||||||
|
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="error"><?php echo htmlspecialchars($error); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($success): ?>
|
||||||
|
<div class="success"><?php echo htmlspecialchars($success); ?></div>
|
||||||
|
<p>You can now <a href="admin.php">go to the Admin page</a> to log in.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<form action="setup.php" method="post">
|
||||||
|
<div>
|
||||||
|
<label for="username">Admin Username:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="username"
|
||||||
|
id="username"
|
||||||
|
value="<?php echo isset($_POST['username']) ? htmlspecialchars($_POST['username']) : ''; ?>"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="password">Password:</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
id="password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="confirm_password">Confirm Password:</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="confirm_password"
|
||||||
|
id="confirm_password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button type="submit" name="setup">Save Admin User</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
|
@ -200,7 +200,7 @@ $makeuser
|
||||||
|
|
||||||
if (mail('root', 'new tilde.club signup', $msgbody)) {
|
if (mail('root', 'new tilde.club signup', $msgbody)) {
|
||||||
echo '<div class="alert alert-success" role="alert">
|
echo '<div class="alert alert-success" role="alert">
|
||||||
email sent! we\'ll get back to you soon with login instructions! (timeframe for processing signups varies greatly) <a href="/">back to tilde.club home</a>
|
email sent! we\'ll get back to you soon (usually within a day) with login instructions! <a href="/">back to tilde.club home</a>
|
||||||
</div>';
|
</div>';
|
||||||
// temp. add to forbidden to prevent double signups (cleanup after user creation)
|
// temp. add to forbidden to prevent double signups (cleanup after user creation)
|
||||||
file_put_contents("/var/signups_current", $name.PHP_EOL, FILE_APPEND);
|
file_put_contents("/var/signups_current", $name.PHP_EOL, FILE_APPEND);
|
||||||
|
|
41
style.css
41
style.css
|
@ -123,7 +123,7 @@ code > span.fl {
|
||||||
background-color: #f93;
|
background-color: #f93;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Navbar linkss */
|
/* Navbar links */
|
||||||
#navbar a {
|
#navbar a {
|
||||||
float: left;
|
float: left;
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -141,47 +141,18 @@ code > span.fl {
|
||||||
|
|
||||||
/* Page content */
|
/* Page content */
|
||||||
.user-list {
|
.user-list {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(195px, 1fr));
|
flex-flow: row wrap;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-list a {
|
.user-list a {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
flex: 10em;
|
flex: 10em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-list a:before {
|
|
||||||
content:"~";
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-op="5"] { opacity:5%; }
|
|
||||||
[data-op="10"] { opacity:10%; }
|
|
||||||
[data-op="15"] { opacity:15%; }
|
|
||||||
[data-op="20"] { opacity:20%; }
|
|
||||||
[data-op="25"] { opacity:25%; }
|
|
||||||
[data-op="30"] { opacity:30%; }
|
|
||||||
[data-op="35"] { opacity:35%; }
|
|
||||||
[data-op="40"] { opacity:40%; }
|
|
||||||
[data-op="45"] { opacity:45%; }
|
|
||||||
[data-op="50"] { opacity:50%; }
|
|
||||||
[data-op="55"] { opacity:55%; }
|
|
||||||
[data-op="60"] { opacity:60%; }
|
|
||||||
[data-op="65"] { opacity:65%; }
|
|
||||||
[data-op="70"] { opacity:70%; }
|
|
||||||
[data-op="75"] { opacity:75%; }
|
|
||||||
[data-op="80"] { opacity:80%; }
|
|
||||||
[data-op="85"] { opacity:85%; }
|
|
||||||
[data-op="90"] { opacity:90%; }
|
|
||||||
[data-op="95"] { opacity:95%; }
|
|
||||||
[data-op="100"] { opacity:100%; }
|
|
||||||
|
|
||||||
.user-list a:hover {
|
|
||||||
text-decoration:underline;
|
|
||||||
color:#f70;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-list b {
|
.user-list b {
|
||||||
background-color: #fb5;
|
background-color: #fb5;
|
||||||
color:#000;
|
color:#000;
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
[
|
[
|
||||||
{"date": "07/20/2025", "name": "mlot", "url": "/~mlot"},
|
|
||||||
{"date": "07/03/2025", "name": "whitcomb", "url": "/~whitcomb"},
|
|
||||||
{"date": "05/06/2025", "name": "mlot", "url": "/~mlot"},
|
|
||||||
{"date": "05/03/2025", "name": "ve3zsh", "url": "/~ve3zsh"},
|
|
||||||
{"date": "02/26/2025", "name": "djhsu", "url": "/~djhsu"},
|
|
||||||
{"date": "11/11/2024", "name": "litemotiv", "url": "/~litemotiv"},
|
{"date": "11/11/2024", "name": "litemotiv", "url": "/~litemotiv"},
|
||||||
{"date": "10/12/2024", "name": "subarctic", "url": "/~subarctic"},
|
{"date": "10/12/2024", "name": "subarctic", "url": "/~subarctic"},
|
||||||
{"date": "09/25/2024", "name": "thumos", "url": "/~thumos"},
|
{"date": "09/25/2024", "name": "thumos", "url": "/~thumos"},
|
||||||
|
|
|
@ -1,24 +1,36 @@
|
||||||
<?php
|
<?php
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
$title = 'tilde.club users';
|
$title = 'tilde.club users';
|
||||||
include __DIR__.'/../header.php';
|
include __DIR__.'/../header.php';
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<h1 id="fancyboi">Tilde.club user list</h1>
|
|
||||||
|
<h1 id="fancyboi">full user list</h1>
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<p>Here is a full list of users (including those who haven't updated their page from the default)</p>
|
|
||||||
<p>Also see users who have updated their page in the <a href="http://tilde.club/tilde.24h.php">last 24 hours</a></p>
|
<p>here's a full list of users (including those who haven't updated their page from the default)</p>
|
||||||
<br>
|
|
||||||
<div class="user-list">
|
<p>see <a href="http://tilde.club/tilde.24h.php">users who have updated their page in the last 24 hours</a></p>
|
||||||
<?php
|
|
||||||
foreach (glob("/home/*") as $user)
|
<br>
|
||||||
{
|
<ul class="user-list">
|
||||||
$user = basename($user);
|
|
||||||
echo '<a href="/~'.$user.'/">'.$user.'</a>';
|
<?php foreach (glob("/home/*") as $user) {
|
||||||
}
|
$user = basename($user); ?>
|
||||||
?>
|
<li><a href="/~<?=$user?>/">~<?=$user?></a></li>
|
||||||
</div>
|
<?php } ?>
|
||||||
|
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php include __DIR__.'/../footer.php';
|
<?php
|
||||||
|
include __DIR__.'/../footer.php';
|
||||||
|
|
||||||
|
|
|
@ -2,59 +2,50 @@
|
||||||
$title = "tilde.club wiki";
|
$title = "tilde.club wiki";
|
||||||
include __DIR__."/../header.php";
|
include __DIR__."/../header.php";
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<h1 id="fancyboi">the tilde.club wiki</h1>
|
<h1 id="fancyboi">the tilde.club wiki</h1>
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<p>Here's the articles on our wiki:</p>
|
|
||||||
<ul>
|
|
||||||
<?php
|
|
||||||
// category order
|
|
||||||
$order = ['tilde.club', 'tutorials', 'software', 'links'];
|
|
||||||
|
|
||||||
$category_to_articles = [];
|
<p>here's the articles on our wiki:</p>
|
||||||
|
<ul>
|
||||||
// get articles
|
<?php
|
||||||
foreach (glob("source/*.md") as $file)
|
$category_to_articles = [];
|
||||||
{
|
foreach (glob("source/*.md") as $file) {
|
||||||
$article = basename($file, ".md");
|
$article = basename($file, ".md");
|
||||||
$title = preg_match("/title: (.*)/i", file_get_contents($file), $matches) ? $matches[1] : $article;
|
$title = preg_match("/title: (.*)/i", file_get_contents($file), $matches) ? $matches[1] : $article;
|
||||||
$title = ucfirst($title);
|
$category = preg_match("/category: (.*)/i", file_get_contents($file), $matches) ? $matches[1] : 'default';
|
||||||
$category = preg_match("/category: (.*)/i", file_get_contents($file), $matches) ? $matches[1] : 'default';
|
if (array_key_exists($category, $category_to_articles)) {
|
||||||
|
array_push($category_to_articles[$category], [$article, $title]);
|
||||||
if (array_key_exists($category, $category_to_articles))
|
} else {
|
||||||
array_push($category_to_articles[$category], [$article, $title]);
|
$category_to_articles[$category] = [[$article, $title]];
|
||||||
else
|
}
|
||||||
$category_to_articles[$category] = [[$article, $title]];
|
|
||||||
|
|
||||||
ksort($category_to_articles);
|
ksort($category_to_articles);
|
||||||
}
|
}
|
||||||
|
?>
|
||||||
foreach ($order as $category)
|
<?php foreach ($category_to_articles as $category => $articles) { ?>
|
||||||
{
|
<li><?=$category?></li>
|
||||||
echo '<li>'.ucwords($category).'</li>';
|
<ul>
|
||||||
echo '<ul>';
|
<?php
|
||||||
|
$article_titles = [];
|
||||||
$article_titles = [];
|
$article_names = [];
|
||||||
$article_names = [];
|
foreach ($articles as $article) {
|
||||||
|
array_push($article_names, $article[0]);
|
||||||
foreach ($category_to_articles[$category] as $article)
|
array_push($article_titles, $article[1]);
|
||||||
{
|
}
|
||||||
array_push($article_names, $article[0]);
|
$name_to_title = array_combine($article_names, $article_titles);
|
||||||
array_push($article_titles, $article[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$name_to_title = array_combine($article_names, $article_titles);
|
|
||||||
asort($name_to_title);
|
asort($name_to_title);
|
||||||
|
foreach ($name_to_title as $name => $title) { ?>
|
||||||
foreach ($name_to_title as $name => $title)
|
<li><a href="/wiki/<?=$name?>.html"><?=$title?></a></li>
|
||||||
echo '<li><a href="/wiki/'.$name.'.html">'.$title.'</a></li>';
|
<?php } ?>
|
||||||
|
</ul>
|
||||||
echo '</ul><br>';
|
|
||||||
|
|
||||||
}
|
<?php } ?>
|
||||||
?>
|
</ul>
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php include "../footer.php";
|
|
||||||
|
<?php include "../footer.php"; ?>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: Using Two-Factor Authentication (2FA) on Tilde.club
|
title: USING Two-Factor Authentication (2FA) ON TILDE.CLUB
|
||||||
author: deepend
|
author: deepend
|
||||||
category: tutorials
|
category: tutorials
|
||||||
---
|
---
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: bashblog (blog platform)
|
title: bashblog
|
||||||
author:
|
author:
|
||||||
- deepend
|
- deepend
|
||||||
- benharri
|
- benharri
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
title: BBJ (Bulletin Butter & Jelly)
|
title: bbj
|
||||||
author: audiodude
|
author: audiodude
|
||||||
category: software
|
category: tutorials
|
||||||
---
|
---
|
||||||
bbj: bulletin butter and jelly, a cozy bbs in your terminal
|
bbj: bulletin butter and jelly, a cozy bbs in your terminal
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: CGI - Making web applications like it's 90s
|
title: "CGI: Making web applications like it's 90s"
|
||||||
category: tutorials
|
category: tutorials
|
||||||
author: xwindows
|
author: xwindows
|
||||||
---
|
---
|
||||||
|
|
|
@ -1,125 +0,0 @@
|
||||||
---
|
|
||||||
title: using cgit on tilde.club
|
|
||||||
author: deepend
|
|
||||||
category: tutorials
|
|
||||||
---
|
|
||||||
|
|
||||||
`cgit` gives every tilde.club member a simple, read‑only web view of their public Git repositories.
|
|
||||||
Any repo you put in `~/public_git/` and end with `.git` is automatically shown at
|
|
||||||
|
|
||||||
```
|
|
||||||
https://tilde.club/~<username>/git/
|
|
||||||
```
|
|
||||||
|
|
||||||
Below is the quick‑start plus a few tips.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1 Create the **public\_git** directory (once)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ mkdir -p ~/public_git
|
|
||||||
```
|
|
||||||
|
|
||||||
The web server is already allowed to traverse `~/public_git`, so you do **not** have to chmod anything manually.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2 Add a repository
|
|
||||||
|
|
||||||
Only **bare** repos are accepted (they have no working tree inside them).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# create a brand‑new bare repository
|
|
||||||
$ cd ~/public_git
|
|
||||||
$ git init --bare hello.git
|
|
||||||
```
|
|
||||||
|
|
||||||
or push an existing project:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ cd ~/my/project
|
|
||||||
# add tilde as a remote and mirror‑push everything
|
|
||||||
$ git remote add tilde ssh://<username>@tilde.club/~/public_git/project.git
|
|
||||||
$ git push --mirror tilde
|
|
||||||
```
|
|
||||||
|
|
||||||
You can repeat for as many repos as you like; just keep each one directly in
|
|
||||||
`~/public_git/` and make sure the name ends with `.git`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3 Browse your repos
|
|
||||||
|
|
||||||
```
|
|
||||||
Index page : https://tilde.club/~<username>/git/
|
|
||||||
Single repo : https://tilde.club/~<username>/git/<repo>.git/
|
|
||||||
```
|
|
||||||
|
|
||||||
Example for user **deepend**:
|
|
||||||
|
|
||||||
```
|
|
||||||
https://tilde.club/~deepend/git/ # lists everything
|
|
||||||
https://tilde.club/~deepend/git/hello.git/ # specific repo
|
|
||||||
```
|
|
||||||
|
|
||||||
The header will say `~deepend Git Repositories`, commits are clickable, diffs
|
|
||||||
are highlighted, and the cloning URL is shown near the top right.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4 Update a repository
|
|
||||||
|
|
||||||
Because the repo is bare you **push** into it; cgit shows the new state
|
|
||||||
immediately.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git push tilde main # normal
|
|
||||||
$ git push --mirror tilde # full mirror (branches + tags)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5 Remove a repository
|
|
||||||
|
|
||||||
Simply delete or rename the directory:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ rm -rf ~/public_git/oldstuff.git
|
|
||||||
```
|
|
||||||
|
|
||||||
The entry disappears from the index on the next page load.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## FAQ
|
|
||||||
|
|
||||||
### Can I hide a repo from the list?
|
|
||||||
|
|
||||||
Move it somewhere else in your home directory or make it non‑bare.
|
|
||||||
`cgit` only scans `~/public_git/*.git`.
|
|
||||||
|
|
||||||
### Why bare repos only?
|
|
||||||
|
|
||||||
Internal `.git/` directories inside non‑bare repos confuse cgit’s scanner and
|
|
||||||
produce broken links like `/repo.git/.git/`.
|
|
||||||
Bare repos avoid that and are the normal way to publish Git over HTTP.
|
|
||||||
|
|
||||||
### Clone / pull URL?
|
|
||||||
|
|
||||||
Use SSH for write access:
|
|
||||||
|
|
||||||
```
|
|
||||||
ssh://<username>@tilde.club/~/public_git/<repo>.git
|
|
||||||
```
|
|
||||||
|
|
||||||
or plain HTTPS for read‑only:
|
|
||||||
|
|
||||||
```
|
|
||||||
https://tilde.club/~<username>/git/<repo>.git
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Happy hacking, and show off your code!
|
|
||||||
Questions? `#club` on irc.newnet.net or mail **[root@tilde.club](mailto:root@tilde.club)**.
|
|
|
@ -4,34 +4,26 @@ author: deepend
|
||||||
category: tilde.club
|
category: tilde.club
|
||||||
---
|
---
|
||||||
|
|
||||||
***NOTICE: Tilde Club is not a registered legal entity or charity, so any donations made cannot be claimed as tax-deductible contributions. ***
|
|
||||||
|
|
||||||
If you like what tilde.club does and would like to contribute to the costs of running such a service, feel free to make a donation. If you can, we appreciate it, if you can't, we also appreciate you being here.
|
If you like what tilde.club does and would like to contribute to the costs of running such a service, feel free to make a donation. If you can, we appreciate it, if you can't, we also appreciate you being here.
|
||||||
|
|
||||||
## Methods you can donate to tilde.club.
|
## Methods you can donate to tilde.club.
|
||||||
|
|
||||||
There are currently two (2) methods you can donate financially to tilde.club. (All money is used towards paying hosting costs (domains, servers)
|
There are currently two (2) methods you can donate financially to tilde.club. (All money is used towards paying hosting costs (domains, servers)
|
||||||
|
|
||||||
[<img src="https://shields.io/badge/kofi-Support_Us-ff5f5f?logo=ko-fi&style=for-the-badgeKofi">](https://ko-fi.com/tildeclub)
|
1. [Liberapay](https://liberapay.com/tilde.club/donate)
|
||||||
|
<img src="https://img.shields.io/liberapay/receives/tilde.club.svg?logo=liberapay"> <img src="https://img.shields.io/liberapay/patrons/tilde.club.svg?logo=liberapay"> <img src="https://img.shields.io/liberapay/goal/tilde.club.svg?logo=liberapay">
|
||||||
|
|
||||||
[](https://www.paypal.com/donate?hosted_button_id=DWHSADKJ26HZ8)
|
2. [Paypal](https://www.paypal.com/donate?hosted_button_id=DWHSADKJ26HZ8)
|
||||||
|
|
||||||
[](https://github.com/sponsors/tildeclub)
|
Our current server costs are $180.00 USD /Month. And will be going up in the coming months due to costs increasing at OVH.
|
||||||
|
(is the reason this page exists again)
|
||||||
[Vultr](https://www.vultr.com/?ref=9732299-9J) Signing up to Vultr using this link gives you $300 credit and it benefits tilde.club ($300 Credit is Limited Time).
|
|
||||||
|
|
||||||
[DigitalOcean](https://www.digitalocean.com/?refcode=be3f8510bfe9&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge) Signing up gets your $200 Credit and helps tilde.club.
|
|
||||||
|
|
||||||
[Web Hosting Canada](https://clients.whc.ca/aff.php?aff=7560) Buying any service/domain helps tilde.club.
|
|
||||||
|
|
||||||
[Voip.ms](https://voip.ms/en/invite/MTEyMDM5) Buying Voip Services gives me credit so I will be able to offer more tilde.tel features in the future.
|
|
||||||
|
|
||||||
**NOTE: Please E-Mail root@tilde.club to notify that you have donated so we can add you to the gold star supporters list.
|
**NOTE: Please E-Mail root@tilde.club to notify that you have donated so we can add you to the gold star supporters list.
|
||||||
|
|
||||||
## Be Involved!
|
## Be Involved!
|
||||||
|
|
||||||
Best thing to do is be involved in the community.
|
Best thing to do is be involved in the community.
|
||||||
Eg. Create a PR on tilde.club's github, Chat on IRC, create awesome webpages,
|
Eg. Create a PR on tilde.club's github or tildegit, Chat on IRC, create awesome webpages,
|
||||||
develop software using the many tools available to you on tilde.club.
|
develop software using the many tools available to you on tilde.club.
|
||||||
|
|
||||||
Tilde.club does cost money to run, however it all makes it worth it if the community is active and enjoying what we offer.
|
Tilde.club does cost money to run, however it all makes it worth it if the community is active and enjoying what we offer.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: custom 404 error page
|
title: custom 404
|
||||||
author: deepend
|
author: deepend
|
||||||
category: tutorials
|
category: tutorials
|
||||||
---
|
---
|
||||||
|
|
|
@ -1,104 +0,0 @@
|
||||||
---
|
|
||||||
title: Tilde.club Help Desk Guide
|
|
||||||
category: tilde.club
|
|
||||||
author: deepend
|
|
||||||
---
|
|
||||||
|
|
||||||
# Tilde.club Help Desk Guide
|
|
||||||
|
|
||||||
Welcome! This Help Desk system provides a quick, self-serve way to:
|
|
||||||
|
|
||||||
1. **Request or redeem a new SSH key** for your tilde account.
|
|
||||||
2. **Reset your account password** (if you've forgotten it).
|
|
||||||
|
|
||||||
Below you’ll find step-by-step instructions on how to use the help desk system when you **SSH** into the `help` user.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Accessing the Help Desk
|
|
||||||
|
|
||||||
1. Open a terminal on your local machine.
|
|
||||||
2. **SSH** into the **help** account (adjust the hostname to your actual tilde server name):
|
|
||||||
```bash
|
|
||||||
ssh help@tilde.club
|
|
||||||
```
|
|
||||||
3. You’ll see a welcome message and a **main menu** with numbered options.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Main Menu Overview
|
|
||||||
|
|
||||||
After logging in, you’ll see three main options:
|
|
||||||
|
|
||||||
1. **SSH Key Help**
|
|
||||||
2. **Password Help**
|
|
||||||
3. **Exit**
|
|
||||||
|
|
||||||
Select the option that applies by typing its corresponding number and pressing **Enter**.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. SSH Key Help
|
|
||||||
|
|
||||||
When you choose **SSH Key Help** at the main menu, you’ll see another menu:
|
|
||||||
|
|
||||||
1. **Request a new SSH key**
|
|
||||||
2. **Redeem a code** for a new SSH key
|
|
||||||
3. **Return** to the previous menu
|
|
||||||
|
|
||||||
### 1.1 Request a New SSH Key
|
|
||||||
|
|
||||||
1. Pick **“I want to request a new SSH key”** (option 1).
|
|
||||||
2. **Enter the email** you registered with your tilde account. The system does a simple check to ensure it’s valid.
|
|
||||||
3. If the email matches an existing account, you’ll receive a **“request code”** at that address.
|
|
||||||
4. After receiving that code, **log out** or press **Enter** to return to the main menu.
|
|
||||||
|
|
||||||
### 1.2 Redeem a New SSH Key
|
|
||||||
|
|
||||||
1. Back in the **SSH Key Help** menu, choose **“I have a code from my email and need to redeem it.”**
|
|
||||||
2. Paste in the **request code** you received.
|
|
||||||
3. The system confirms your username.
|
|
||||||
4. When prompted, **paste your new public SSH key** (the part that starts with `ssh-ed25519` or `ssh-rsa` or similar).
|
|
||||||
5. The system appends your key to your `~/.ssh/authorized_keys`.
|
|
||||||
6. You’ll see a success message, and you can then **log in** to your tilde with that new key.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Password Help
|
|
||||||
|
|
||||||
At the main menu, choosing **Password Help** displays:
|
|
||||||
|
|
||||||
1. **Request a password reset code**
|
|
||||||
2. **Redeem a password reset code**
|
|
||||||
3. **Return** to the previous menu
|
|
||||||
|
|
||||||
### 2.1 Request a Password Reset Code
|
|
||||||
|
|
||||||
1. Choose **“Request a password reset code.”**
|
|
||||||
2. **Enter your email** address.
|
|
||||||
3. The system sends a **reset code** to your email if the account matches.
|
|
||||||
4. Exit or return to the menu once you have the code.
|
|
||||||
|
|
||||||
### 2.2 Redeem a Password Reset Code
|
|
||||||
|
|
||||||
1. Choose **“Redeem a password reset code.”**
|
|
||||||
2. **Paste in** the code from your email.
|
|
||||||
3. Enter a **new password** for your tilde account, then **confirm** it.
|
|
||||||
4. Upon success, the system updates your account’s password immediately.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Exiting the Help Desk
|
|
||||||
|
|
||||||
Simply choose **“I’d like to leave this help desk”** (option 3 in the main menu) or press <kbd>Ctrl</kbd>+<kbd>D</kbd> (end of file) to disconnect.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tips & Troubleshooting
|
|
||||||
|
|
||||||
- **Time Limit**: Each prompt has a 2-minute inactivity timer. If you wait too long, the help desk **exits** automatically. Just log in again to resume.
|
|
||||||
- **Email Didn’t Arrive?** Check your spam folder. If you still don’t see it, contact root@tilde.club.
|
|
||||||
- **Invalid Email**: If you mistype an email or use an unrecognized domain, you’ll see an error. Double-check your address.
|
|
||||||
- **Incorrect Code**: If the code was typed incorrectly or expired, the system will refuse it. Request a new one if needed.
|
|
||||||
|
|
||||||
---
|
|
|
@ -9,20 +9,15 @@ category: tutorials
|
||||||
|
|
||||||
## irc
|
## irc
|
||||||
|
|
||||||
Our main channel is on the [Newnet IRC Network](https://newnet.net).
|
Our main channel is on the [Newnet IRC Network](https://newnet.net). the official
|
||||||
The official channel for ~club is `#club`. Stop by and say hello!
|
channel for ~club is `#club`. Stop by and say hello!
|
||||||
|
|
||||||
> **New!** An *official secondary* channel is now open on the Zoite IRC
|
|
||||||
> Network in case Newnet ever has an outage (or if you just feel like
|
|
||||||
> hanging out elsewhere). Connect to **irc.zoite.net** on port **6670 SSL**
|
|
||||||
> and `/join #club` – same welcoming vibe, different network.
|
|
||||||
|
|
||||||
run `chat` to open [weechat](https://weechat.org) auto-connected to our irc
|
run `chat` to open [weechat](https://weechat.org) auto-connected to our irc
|
||||||
server. try launching [tmux](tmux.html), [byobu](https://superuser.com/a/423397)
|
server. try launching [tmux](tmux.html), [byobu](https://superuser.com/a/423397)
|
||||||
or [screen](screen.html) to keep your chat session running.
|
or [screen](screen.html) to keep your chat session running.
|
||||||
|
|
||||||
other clients like irssi are available as well! just connect to **irc.newnet.net**
|
other clients like irssi are available as well! just connect to localhost on
|
||||||
on port **6697 TLS** and `/join #club`.
|
port 6667 and `/join #club`. If your client defaults to enabling TLS, you'll need to specify that it shouldn't use TLS.
|
||||||
|
|
||||||
feel free to use Newnet's [webchat](https://web.newnet.net/?join=club) if
|
feel free to use Newnet's [webchat](https://web.newnet.net/?join=club) if
|
||||||
you prefer.
|
you prefer.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: Byobu, TMUX & Screen (terminal multiplexers)
|
title: Terminal Multiplexers
|
||||||
author: rudi
|
author: rudi
|
||||||
category: software
|
category: software
|
||||||
---
|
---
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
---
|
|
||||||
title: Adding a web counter to your page
|
|
||||||
author: deepend
|
|
||||||
category: tutorials
|
|
||||||
---
|
|
||||||
|
|
||||||
To display a visit counter on your webpage, you'll need to edit your `index.html` file and insert a `<script>` tag that loads the counter.
|
|
||||||
|
|
||||||
Type: `nano index.html` to open your file for editing.
|
|
||||||
|
|
||||||
Add the following line *exactly where you want the counter to appear*:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<script src="https://counter.tilde.club/?page=homepage&user=yourname&style=57chevy"></script>
|
|
||||||
```
|
|
||||||
|
|
||||||
- Replace `homepage` with any identifier for the page you're tracking.
|
|
||||||
- Replace `yourname` with your username or something unique to your site.
|
|
||||||
- Replace `57chevy` with a different style if you'd like.
|
|
||||||
|
|
||||||
Here’s an example:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<p>Visitor count:
|
|
||||||
<script src="https://counter.tilde.club/?page=homepage&user=alice&style=web1"></script>
|
|
||||||
</p>
|
|
||||||
```
|
|
||||||
|
|
||||||
Once you’ve added the line, save and close the file using `CTRL+X`, then press `y` and `[Enter]`.
|
|
||||||
|
|
||||||
Refresh your site in your browser to see the counter.
|
|
||||||
|
|
||||||
### Available Styles
|
|
||||||
|
|
||||||
You can use any of these style names in the `style=` parameter:
|
|
||||||
|
|
||||||
- `57chevy`
|
|
||||||
- `7seg`
|
|
||||||
- `bbldotg`
|
|
||||||
- `bellbtm`
|
|
||||||
- `blgrv`
|
|
||||||
- `cntdwn`
|
|
||||||
- `computer`
|
|
||||||
- `ds9`
|
|
||||||
- `fdb`
|
|
||||||
- `led`
|
|
||||||
- `links`
|
|
||||||
- `marsil`
|
|
||||||
- `sbgs`
|
|
||||||
- `web1`
|
|
||||||
|
|
||||||
Try different styles to see which one fits your site best.
|
|
||||||
|
|
||||||
Note: The counter uses sessions and cookies to count *unique* visits per user every 24 hours (or whatever time is configured). Page refreshes won’t increase the count unless unique mode is off.
|
|
||||||
|
|
||||||
If you need help or want a custom style added, reach out to the site admin.
|
|
||||||
```
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
author: benharri
|
author: benharri
|
||||||
title: How to connect using SSH (secure shell)
|
title: ssh
|
||||||
category: tutorials
|
category: tutorials
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -161,13 +161,13 @@ ssh-keygen -t ed25519 -a 100
|
||||||
|
|
||||||
Note: %USERPROFILE% is a short code that the computer expands to mean C:\\Users\\your_name\\, or whatever the relevant path is to your user's main folder.
|
Note: %USERPROFILE% is a short code that the computer expands to mean C:\\Users\\your_name\\, or whatever the relevant path is to your user's main folder.
|
||||||
|
|
||||||
Next we will open up the public key so we can copy its contents.
|
Next we will open up the public key so we can copy its contents.
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
notepad %USERPROFILE%\.ssh\id_ed25519.pub
|
notepad %USERPROFILE%\.ssh\id_ed25519.pub
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Copy the text of the pubkey that opens in Notepad and paste it in the sshkey field on the signup form or email it to the relevant sign-up address for the tilde you are joining.
|
4. Copy the text of the pubkey that opens in Notepad and paste it in the sshkey field on the signup form or email it to the relevant sign-up address for the tilde you are joining.
|
||||||
|
|
||||||
#### using your keypair
|
#### using your keypair
|
||||||
|
|
||||||
|
@ -183,13 +183,6 @@ ssh username@tilde.club
|
||||||
|
|
||||||
where username is your username (~benharri would use `ssh benharri@tilde.club`)
|
where username is your username (~benharri would use `ssh benharri@tilde.club`)
|
||||||
|
|
||||||
|
|
||||||
Note: If you generated your key to the location above (%USERPROFILE%\\.ssh\\...) then you will be able to SSH to your tilde server without having to specify the location of the key. That folder is the default used by SSH and it will be found automatically. If you generated your keys in a different location or moved them, you will need to specify the full path to the private key.
|
|
||||||
|
|
||||||
```cmd
|
|
||||||
ssh -i c:\path\to\my\private\key username@your.tilde
|
|
||||||
```
|
|
||||||
|
|
||||||
7. profit???
|
7. profit???
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -220,7 +213,7 @@ be located at `~/.ssh/id_ed25519.pub` and `~/.ssh/id_ed25519`
|
||||||
cat ~/.ssh/id_ed25519.pub
|
cat ~/.ssh/id_ed25519.pub
|
||||||
```
|
```
|
||||||
|
|
||||||
4. copy the output of the last command and paste it in the sshkey field on the
|
4. copy the output of the last command and paste it in the sshkey field on the
|
||||||
signup form (or email it to [root@tilde.club](mailto:root@tilde.club) if you already have an account)
|
signup form (or email it to [root@tilde.club](mailto:root@tilde.club) if you already have an account)
|
||||||
|
|
||||||
#### using your keypair
|
#### using your keypair
|
||||||
|
@ -237,6 +230,12 @@ where username is your username (~benharri would use `ssh benharri@tilde.club`)
|
||||||
ssh username@tilde.club
|
ssh username@tilde.club
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note: If you generated your key to the location above (%USERPROFILE%\\.ssh\\...) then you will be able to SSH to your tilde server without having to specify the location of the key. That folder is the default used by SSH and it will be found automatically. If you generated your keys in a different location or moved them, you will need to specify the full path to the private key.
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
ssh -i c:\path\to\my\private\key username@your.tilde
|
||||||
|
```
|
||||||
|
|
||||||
7. profit???
|
7. profit???
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
author: jeffbonhag
|
author: jeffbonhag
|
||||||
title: SSHFS (SSH Filesystem)
|
title: SSHFS
|
||||||
category: software
|
category: software
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: tin (UseNet reader)
|
title: tin
|
||||||
category: software
|
category: software
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
title: ttbp / feels (blog platform)
|
title: ttbp (feels)
|
||||||
author: benharri
|
author:
|
||||||
|
- benharri
|
||||||
category: software
|
category: software
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
---
|
|
||||||
title: The Vi Editor
|
|
||||||
category: software
|
|
||||||
author:
|
|
||||||
- ant
|
|
||||||
---
|
|
||||||
|
|
||||||
*Tilde.Club* has
|
|
||||||
[The Traditional Vi](https://ex-vi.sourceforge.net/)
|
|
||||||
installed on its premises.
|
|
||||||
It is not just another
|
|
||||||
[Vi clone](https://texteditors.org/cgi-bin/wiki.pl?ViFamily),
|
|
||||||
but the direct continuation
|
|
||||||
of Bill Joy's legendary work at Berkeley.
|
|
||||||
The binaries are in `/usr/archaic/bin/`
|
|
||||||
and the man pages
|
|
||||||
(separately for `ex` and `vi`)
|
|
||||||
in `/usr/archaic/share/man/man/`.
|
|
||||||
|
|
||||||
You can invoke *The Traditional Vi* in several ways
|
|
||||||
(in the order of increased engagement):
|
|
||||||
|
|
||||||
1. by the full path to the executable:
|
|
||||||
|
|
||||||
/usr/archaic/bin/vi
|
|
||||||
|
|
||||||
2. by adding it as an alias to your shell's `rc` file
|
|
||||||
(`~/.bashrc` for *Bash*), e.g:
|
|
||||||
|
|
||||||
alias tvi=/usr/archaic/bin/vi
|
|
||||||
|
|
||||||
and then invokng *Vi* by typing `tvi`,
|
|
||||||
|
|
||||||
3. by adding the locations *Vi* and its documentation
|
|
||||||
in front of the `PATH` and `MANPATH` environment variables
|
|
||||||
in your shell's profile script
|
|
||||||
(for *Bash*, `~/.bash_profile` or `~/.profile`):
|
|
||||||
|
|
||||||
export PATH="/usr/archaic/bin/:$PATH"
|
|
||||||
export MANPATH="/usr/archaic/share/man/man:$MANPATH"
|
|
||||||
|
|
||||||
The latter method has the advantage
|
|
||||||
of affecting subshells,
|
|
||||||
so that if you specify `vi` as the default editor
|
|
||||||
in your e-mail or news client, or another CLI program,
|
|
||||||
it will invoke *The Traditional Vi*,
|
|
||||||
ditto for your shell scripts
|
|
||||||
and the `EDITOR` environment variale.
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
|
|
||||||
1. Bill Joy's
|
|
||||||
[An Introduction to Display Editing with Vi ](https://ex-vi.sourceforge.net/viin/paper.html),
|
|
||||||
2. [`vi(1)`](https://ex-vi.sourceforge.net/vi.html) man page,
|
|
||||||
3. [`ex(1)`](https://ex-vi.sourceforge.net/ex.html) man page,
|
|
||||||
4. [A concise `vi` reference](http://www.ungerhu.com/jxh/vi.html),
|
|
||||||
5. [The Ultimate guide to the VI and EX text editors](https://archive.org/details/ultimateguidetov0000unse_i5e4) (a paper book),
|
|
||||||
6. The `#vi` channel on the
|
|
||||||
[Libera.Chat](https://libera.chat/)
|
|
||||||
IRC network,
|
|
||||||
dedicated to the original *Vi*
|
|
||||||
and all its variants except *Vim* & co,
|
|
||||||
7. [VI experience in the shell](https://deut-erium.github.io/2024/01/28/inputrc.html).
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
*The Traditional Vi* is surprisingly easy to build from
|
|
||||||
[its source](https://sourceforge.net/p/ex-vi/code/).
|
|
||||||
You only need to locate the following line in `Makefile`:
|
|
||||||
|
|
||||||
TERMLIB = termlib
|
|
||||||
|
|
||||||
and replace the value with `curses` or `ncurses`,
|
|
||||||
depending on your preferred terminal library.
|
|
||||||
Now you can build and install the project with:
|
|
||||||
|
|
||||||
make && make install
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
title: Vim (editor) .vimrc file
|
title: .vimrc file
|
||||||
category: software
|
category: tutorials
|
||||||
---
|
---
|
||||||
|
|
||||||
The file `.vimrc` in your home directory has instructions for [[vim]]
|
The file `.vimrc` in your home directory has instructions for [[vim]]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
title: Don't use VPN services
|
title: Don't use VPN services
|
||||||
author: joepie91 on github
|
author: joepie91 on github
|
||||||
category: tutorials
|
category: tilde.club
|
||||||
---
|
---
|
||||||
|
|
||||||
# Don't use VPN services.
|
# Don't use VPN services.
|
||||||
|
|
|
@ -14,11 +14,10 @@
|
||||||
<meta name="keywords" content="$for(keywords)$$keywords$$sep$, $endfor$" />
|
<meta name="keywords" content="$for(keywords)$$keywords$$sep$, $endfor$" />
|
||||||
$endif$
|
$endif$
|
||||||
<title>$if(title-prefix)$$title-prefix$ – $endif$$pagetitle$</title>
|
<title>$if(title-prefix)$$title-prefix$ – $endif$$pagetitle$</title>
|
||||||
<link rel="stylesheet" href="https://tilde.club/style.css">
|
<link rel="stylesheet" href="../style.css">
|
||||||
<link rel="icon" type="image/png" href="https://tilde.club/favicon.png">
|
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
code{background: rgba(255, 187, 85, 0.15);padding: 0.1em 0.2em;border-radius: 0.3em;white-space: pre-wrap;}
|
code{background: rgba(255, 187, 85, 0.15);padding: 0.1em 0.2em;border-radius: 0.3em;white-space: pre-wrap;}
|
||||||
pre code{background: none; padding: 0;}
|
pre code{background: none;}
|
||||||
span.smallcaps{font-variant: small-caps;}
|
span.smallcaps{font-variant: small-caps;}
|
||||||
span.underline{text-decoration: underline;}
|
span.underline{text-decoration: underline;}
|
||||||
div.column{display: inline-block; vertical-align: top; width: 50%;}
|
div.column{display: inline-block; vertical-align: top; width: 50%;}
|
||||||
|
|
Loading…
Reference in New Issue