Initial commit x2
This commit is contained in:
parent
eeb058d7f3
commit
59ad480879
@ -1,3 +1,5 @@
|
||||
# ht-dev.de
|
||||
|
||||
Source for the main Website
|
||||
|
||||
Currently this still uses mustache, I plan on migrating everything to handlebars as soon as possible
|
7
config.json
Normal file
7
config.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"port": 8080,
|
||||
"logging": false,
|
||||
"logfile": "server.log",
|
||||
"trustedProxies": [],
|
||||
"webconfig": "./pages.jsonc"
|
||||
}
|
349
index.js
Normal file
349
index.js
Normal file
@ -0,0 +1,349 @@
|
||||
// HT-Web Server (possibly the new Web Framework)
|
||||
|
||||
import fs from 'fs';
|
||||
import express from 'express';
|
||||
import superagent from 'superagent';
|
||||
import { containsCidr } from 'cidr-tools';
|
||||
import { join, resolve, dirname } from 'path';
|
||||
import { parse } from "jsonc-parser";
|
||||
import morgan from 'morgan';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { mustache } from "consolidate";
|
||||
import { renderFile, render as ejsRender } from 'ejs';
|
||||
import { engine as handlebars, create } from 'express-handlebars';
|
||||
import NodeCache from 'node-cache';
|
||||
|
||||
import config from './config.json' with {
|
||||
type: "json"
|
||||
};
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// allow comments in pages file
|
||||
const pages = parse(fs.readFileSync(config.webconfig).toString());
|
||||
|
||||
const cache = new NodeCache({
|
||||
stdTTL: 3600,
|
||||
useClones: false
|
||||
});
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(morgan('dev')); // development and console logging
|
||||
if (config.logging) {
|
||||
// Ensure log file directory exists
|
||||
const logDir = dirname(join(process.cwd(), config.logFile));
|
||||
if (!fs.existsSync(logDir)) {
|
||||
fs.mkdirSync(logDir, { recursive: true });
|
||||
}
|
||||
fs.appendFileSync(join(process.cwd(), config.logfile), `Server started at ${new Date().toUTCString()}\n`);
|
||||
app.use(morgan('combined', {
|
||||
stream: fs.createWriteStream(join(process.cwd(), config.logfile), { flags: 'a' })
|
||||
})); // production
|
||||
process.once('SIGINT', () => {
|
||||
fs.appendFileSync(join(process.cwd(), config.logfile), `Server stopped at ${new Date().toUTCString()}\n`);
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader('Server', "HT Web-Framework V2");
|
||||
res.setHeader('X-Powered-By', "HT Web-Framework V2");
|
||||
next();
|
||||
});
|
||||
|
||||
app.use((req, res, next) => {
|
||||
// Enable for choosing framing options, not migrated yet
|
||||
// if (config.management.embedSite) {
|
||||
// res.setHeader('X-Frame-Options', 'ALLOW-FROM ' + config.management.embedSite);
|
||||
// res.setHeader('Content-Security-Policy', 'frame-ancestors ' + config.management.embedSite);
|
||||
// } else {
|
||||
// res.setHeader('X-Frame-Options', 'DENY');
|
||||
// res.setHeader('Content-Security-Policy', 'frame-ancestors \'none\'');
|
||||
// }
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
// res.setHeader('Content-Security-Policy', 'default-src \'self\'');
|
||||
res.setHeader('Referrer-Policy', 'same-origin');
|
||||
res.setHeader('Feature-Policy', 'geolocation \'none\'; microphone \'none\'; camera \'none\'; speaker \'none\'; vibrate \'none\'; payment \'none\'; usb \'none\';');
|
||||
res.setHeader('X-Permitted-Cross-Domain-Policies', 'none');
|
||||
next();
|
||||
});
|
||||
|
||||
// real IP assignment
|
||||
app.use(async (req, res, next) => {
|
||||
let ip = "0.0.0.0/0";
|
||||
if ("x-forwarded-for" in req.headers) {
|
||||
if (containsCidr(["127.0.0.1", "::1", ...config.trustedProxies], req.ip)) {
|
||||
ip = req.headers['x-forwarded-for'] || req.ip;
|
||||
} else {
|
||||
console.warn("Proxy IP not in list:", req.ip);
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
} else if ("cf-connecting-ip" in req.headers){
|
||||
if (!cache.has("cfcidrList")) {
|
||||
cache.set("cfcidrList", (await superagent.get("https://api.cloudflare.com/client/v4/ips")).body);
|
||||
}
|
||||
const cfcidrList = cache.get("cfcidrList");
|
||||
if (!cfcidrList.success) {
|
||||
return next(cfcidrList.errors.join(", "));
|
||||
}
|
||||
if (containsCidr([...cfcidrList.result.ipv4_cidrs, ...cfcidrList.result.ipv6_cidrs], req.ip)) {
|
||||
ip = req.headers['cf-connecting-ip'] || req.ip;
|
||||
} else {
|
||||
console.warn("CF IP not in list:", req.ip);
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
} else {
|
||||
ip = req.ip; // Do nothing
|
||||
}
|
||||
req.realIp = ip;
|
||||
next();
|
||||
});
|
||||
|
||||
app.set('views', join(__dirname, 'views'));
|
||||
app.engine('mustache', mustache);
|
||||
app.engine('ejs', renderFile);
|
||||
app.engine('handlebars', handlebars());
|
||||
app.set('view engine', pages.settings.defaultType);
|
||||
const extension = "." + pages.settings.defaultType;
|
||||
const hbs = create({
|
||||
extname: ".handlebars",
|
||||
// defaultLayout: 'base.mustache',
|
||||
defaultLayout: false,
|
||||
layoutsDir: join(__dirname, "private", "templates"),
|
||||
partialsDir: join(__dirname, "private", "templates"),
|
||||
});
|
||||
app.use(express.json());
|
||||
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
app.use("/static", express.static(join(process.cwd(), "public")));
|
||||
|
||||
/**
|
||||
* Send a page to the client
|
||||
* @param {import('express').Request} req Express Request
|
||||
* @param {import('express').Response} res Express Response
|
||||
* @param {string} view Name of the view/file to render
|
||||
* @param {object} context context to pass to the template, can be modified
|
||||
* @param {Function} [cb] (optional) callback
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function render(req, res, view, context, cb) {
|
||||
// Inject Servers
|
||||
context.pages = [...pages.navpages];
|
||||
|
||||
// Active Detection
|
||||
context.pages.forEach(element => {
|
||||
element.active = req.url == element.url;
|
||||
});
|
||||
|
||||
if (context.type == "html") {
|
||||
console.debug("Rendering HTML");
|
||||
res.sendFile(join(viewsDir, view + ".html"), { headers: { "Content-Type": "text/html" } });
|
||||
return;
|
||||
} else if (context.type == "ejs") {
|
||||
console.debug("Rendering EJS");
|
||||
const ejsResult = await renderFile(join(viewsDir, view + ".ejs"), context);
|
||||
partials['view'] = join(partialsDir, 'ejs.mustache');
|
||||
res.render(partials['base'], { ...context, partials: partials, ejs: ejsResult }, cb);
|
||||
return;
|
||||
} else if (context.type == "handlebars") {
|
||||
console.debug("Rendering Handlebars");
|
||||
const handlebarsResult = await hbs.renderView(join(viewsDir, view + ".handlebars"), context);
|
||||
partials['view'] = join(partialsDir, 'handlebars.mustache');
|
||||
res.render(partials['base'], { ...context, partials: partials, handlebars: handlebarsResult }, cb);
|
||||
return;
|
||||
} else if (context.type == "mustache") {
|
||||
// Add current view
|
||||
partials['view'] = join(viewsDir, view + ".mustache");
|
||||
res.render(partials['base'], { ...context, partials: partials }, cb);
|
||||
} else {
|
||||
throw new Error("Unknown template type: " + context.type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check for site access here, if allowed, call {@link render()}
|
||||
* @param {import('express').Request} req Express Request
|
||||
* @param {import('express').Response} res Express Response
|
||||
* @param {string} view Name of the view/file to render
|
||||
* @param {object} context context to pass to the template, can be modified
|
||||
* @param {string[]} perms Array of permissions that need to be fulfilled by the user
|
||||
* @param {Function} [cb] (optional) callback
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function renderRestricted(req, res, view, context, perms, cb) {
|
||||
res.sendStatus(500);
|
||||
throw new Error("Not implemented yet");
|
||||
}
|
||||
|
||||
function isJSON(str) {
|
||||
try {
|
||||
return JSON.stringify(str) && !!str;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Read partial templates
|
||||
const partialsDir = join(__dirname, "private", "templates");
|
||||
|
||||
const viewsDir = join(__dirname, "private", "views");
|
||||
|
||||
// Map Partials
|
||||
const partials = {}
|
||||
fs.readdirSync(partialsDir).map(part => partials[part.replace(extension, '')] = join(partialsDir, part));
|
||||
|
||||
// Generate Routes
|
||||
for (const path in pages.paths.get) {
|
||||
if (Object.hasOwn(pages.paths.get, path)) {
|
||||
const element = pages.paths.get[path];
|
||||
if (element.file) {
|
||||
app.get(path, async (req, res, next) => {
|
||||
let patches = {
|
||||
type: element.type ?? pages.settings.defaultType,
|
||||
}
|
||||
if (element.scripts) {
|
||||
try {
|
||||
for await (const file of element.scripts) {
|
||||
const module = await import("file://" + resolve(join("private", "scripts", file + ".js")));
|
||||
let result = module.get ? await module.get(element, { req, res, next }, config) : await module.default(element, { req, res, next }, config);
|
||||
if (isJSON(result) && !(result?.done ?? false)) {
|
||||
patches = { ...patches, ...result }
|
||||
} else {
|
||||
console.debug("Request already handled, returning...")
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
if (element.settings && element.settings.subtitle) {
|
||||
patches.title = element.settings.subtitle + " | " + settings.title;
|
||||
}
|
||||
const context = { ...settings, ...element.settings, ...patches };
|
||||
if (element.restriction) {
|
||||
renderRestricted(req, res, element.file, context, element.restriction);
|
||||
} else {
|
||||
// TODO: add way to handle the script already handling requests and then skip this
|
||||
try {
|
||||
await render(req, res, element.file, context);
|
||||
} catch (error) {
|
||||
// TODO: check why errors are just ignored
|
||||
console.debug(error);
|
||||
// return next(error);
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
app.get(path, async (req, res, next) => {
|
||||
if (element.scripts) {
|
||||
try {
|
||||
for await (const file of element.scripts) {
|
||||
const module = await import("file://" + resolve(join('web', 'scripts', file + ".js")));
|
||||
module.get ? await module.get(element, { req, res, next }, config) : await module.default(element, { req, res, next }, config);
|
||||
}
|
||||
} catch (error) {
|
||||
return next(error);
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const path in pages.paths.post) {
|
||||
if (Object.hasOwn(pages.paths.post, path)) {
|
||||
const element = pages.paths.post[path];
|
||||
if (element.file) {
|
||||
app.post(path, async (req, res, next) => {
|
||||
let patches = {
|
||||
type: element.type ?? pages.settings.defaultType,
|
||||
}
|
||||
if (element.scripts) {
|
||||
try {
|
||||
for await (const file of element.scripts) {
|
||||
const module = await import("file://" + resolve(join('web', 'scripts', file + ".js")));
|
||||
let result = module.post ? await module.post(element, { req, res, next }, config) : await module.default(element, { req, res, next }, config);
|
||||
if (isJSON(result) && !(result?.done ?? false)) {
|
||||
patches = { ...patches, ...result }
|
||||
} else {
|
||||
console.debug("Request already handled, returning...")
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
if (element.settings && element.settings.subtitle) {
|
||||
patches.title = element.settings.subtitle + " | " + settings.title;
|
||||
}
|
||||
const context = { ...settings, ...element.settings, ...patches };
|
||||
if (element.restriction) {
|
||||
renderRestricted(req, res, element.file, context, element.restriction);
|
||||
} else {
|
||||
// TODO: add way to handle the script already handling requests and then skip this
|
||||
try {
|
||||
await render(req, res, element.file, context);
|
||||
} catch (error) {
|
||||
// TODO: check why errors are just ignored
|
||||
console.debug(error);
|
||||
// return next(error);
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
app.post(path, async (req, res, next) => {
|
||||
if (element.scripts) {
|
||||
try {
|
||||
for await (const file of element.scripts) {
|
||||
const module = await import("file://" + resolve(join('web', 'scripts', file + ".js")));
|
||||
module.post ? await module.post(element, { req, res, next }, config) : await module.default(element, { req, res, next }, config);
|
||||
}
|
||||
} catch (error) {
|
||||
return next(error);
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let settings = {
|
||||
...pages.settings,
|
||||
pages: [...pages.navpages],
|
||||
};
|
||||
|
||||
app.get('/', async (req, res) => {
|
||||
try {
|
||||
const currentConfigFile = await readFile(join(process.cwd(), 'config.json'), 'utf-8');
|
||||
const currentConfig = JSON.parse(currentConfigFile);
|
||||
res.json(currentConfig);
|
||||
} catch (error) {
|
||||
console.error('Error reading config file:', error);
|
||||
res.status(500).json({ error: 'Failed to read configuration' });
|
||||
}
|
||||
});
|
||||
|
||||
// Error handling middleware
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('Error:', err);
|
||||
// res.status(500).json({
|
||||
// error: 'Internal Server Error',
|
||||
// message: err.message,
|
||||
// stack: err.stack
|
||||
// });
|
||||
res.status(500).send(`<title>ERR: ${err.message}</title><h1>500: Internal Server Error</h1><p>${err.stack.replaceAll('\n', "<br />")}</p>`);
|
||||
});
|
||||
|
||||
app.listen(config.port, () => {
|
||||
console.log(`Server listening on port ${config.port}`);
|
||||
});
|
||||
|
2028
package-lock.json
generated
Normal file
2028
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
package.json
Normal file
29
package.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "ht-dev.de",
|
||||
"version": "1.0.0",
|
||||
"description": "Source for the main Website",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.ht-dev.de/Hammer1279/ht-dev.de.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cidr-tools": "^11.0.3",
|
||||
"consolidate": "^1.0.4",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^5.1.0",
|
||||
"express-handlebars": "^8.0.1",
|
||||
"jsonc-parser": "^3.3.1",
|
||||
"morgan": "^1.10.0",
|
||||
"mustache": "^4.2.0",
|
||||
"node-cache": "^5.1.2",
|
||||
"superagent": "^10.2.0"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
34
pages.jsonc
Normal file
34
pages.jsonc
Normal file
@ -0,0 +1,34 @@
|
||||
// Defines the routes used by the Webserver
|
||||
{
|
||||
"settings": {
|
||||
"title": "Title", // tab title
|
||||
"headerTitle": "Header Title", // title in navbar
|
||||
// base_* are the default stylesheets and scripts (client side) to load on every page
|
||||
"base_stylesheets": [], // stylesheets that will always be loaded
|
||||
"base_scripts": [], // scripts that will always be loaded
|
||||
"widgets": [], // widgets are the easiest way for quick dynamic data, but should otherwise be avoided
|
||||
"defaultType": "handlebars" // default template engine to use
|
||||
},
|
||||
"paths": {
|
||||
"get": {
|
||||
"/": {
|
||||
"file": "home",
|
||||
"type": "html", // possible values: "mustache", "handlebars", "ejs", "html"
|
||||
"scripts": [],
|
||||
"settings": { // these are part of the context of a object
|
||||
"stylesheets": [],
|
||||
"scripts": [],
|
||||
"widgets": []
|
||||
},
|
||||
"restriction": false
|
||||
}
|
||||
},
|
||||
"post": {}
|
||||
},
|
||||
"navpages": [
|
||||
{
|
||||
"name": "Home",
|
||||
"url": "/"
|
||||
}
|
||||
]
|
||||
}
|
40
private/templates/base.handlebars
Normal file
40
private/templates/base.handlebars
Normal file
@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{title}}</title>
|
||||
<link rel="icon" type="image/jpg" href="/static/img/logo.jpg" />
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
{{#base_stylesheets}}
|
||||
<link rel="stylesheet" href="/static/css/{{&.}}">
|
||||
{{/base_stylesheets}}
|
||||
{{#stylesheets}}
|
||||
<link rel="stylesheet" href="/static/css/{{&.}}">
|
||||
{{/stylesheets}}
|
||||
<script async src="https://cdn.jsdelivr.net/npm/es-module-shims@1/dist/es-module-shims.min.js" crossorigin="anonymous"></script>
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"@popperjs/core": "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/esm/popper.min.js",
|
||||
"js-cookie": "https://cdn.jsdelivr.net/npm/js-cookie@3.0.5/+esm",
|
||||
"superagent": "https://cdn.jsdelivr.net/npm/superagent@10.1.0/+esm"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<meta name="description" content="{{description}}">
|
||||
<meta name="author" content="©2025 Hammer1279">
|
||||
<meta name="robots" content="index,follow">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="{{header}}" />
|
||||
<meta property="og:description" content="{{description}}" />
|
||||
<meta property="og:url" content="https://swprobuilders.com" />
|
||||
<meta property="og:image" content="/static/img/logo.jpg" />
|
||||
<meta property="og:site_name" content="{{title}}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="color-scheme" content="dark">
|
||||
<meta name="theme-color" content="#0f2537">
|
||||
{{! <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.esm.min.js" crossorigin="anonymous"></script> }}
|
||||
</head>
|
66
private/views/home.html
Normal file
66
private/views/home.html
Normal file
@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>HT-Dev DE</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<meta name="description" content="{{description}}">
|
||||
<meta name="author" content="©2025 Hammer1279">
|
||||
<meta name="robots" content="index,follow">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="color-scheme" content="dark">
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="max-width: 70%; margin: 0 auto; padding: 20px; border: 1px solid #ccc; border-radius: 5px;">
|
||||
<div class="center">
|
||||
<h1>HT-Dev.DE</h1>
|
||||
<h2>Hello World! Hello from Germany!</h2>
|
||||
<p>My personal effort for decentralizing and personalizing the web again.</p>
|
||||
<p>This page is in honor to the old (and free) internet.</p>
|
||||
</div>
|
||||
<br>
|
||||
<p>I'm mostly a backend developer, so forgive this crude website, its backend is really good tho :D</p>
|
||||
<p>However you have found this domain, welcome! We have lots of services here:</p>
|
||||
<ul style="text-align: start;">
|
||||
<li><a href="https://chat.ht-dev.de" target="_blank">Matrix Homeserver</a></li>
|
||||
<li><a href="https://git.ht-dev.de" target="_blank">Gitea</a></li>
|
||||
<li><a href="https://cloud.ht-dev.de" target="_blank">Nextcloud</a></li>
|
||||
<li><a href="#" style="color: gray; pointer-events: none; text-decoration: none;">Compute (currently out of service)</a></li>
|
||||
<li><a href="https://mail.ht-dev.de" target="_blank">E-Mail (redirect for using @ht-dev.de accounts)</a></li>
|
||||
<li><a href="telnet://ht-dev.de" style="color: gray; pointer-events: none; text-decoration: none;">Telnet BBS-ish Server (getting reworked)</a></li>
|
||||
</li>
|
||||
</ul>
|
||||
<p>Personal Open-Source Projects to check out:</p>
|
||||
<ul style="text-align: start;">
|
||||
<li><a href="https://github.com/Hammer1279/node-proxy-manager" target="_blank">Node-Proxy-Manager (this site is behind this)</a></li>
|
||||
<li><a href="https://git.ht-dev.de/Hammer1279/node-telnet-client" target="_blank">A Node.js Telnet client with encryption with our own telnet server</a></li>
|
||||
</ul>
|
||||
<p>Hosted Sites via our Servers:</p>
|
||||
<ul style="text-align: start;">
|
||||
<li>ht-dev.de (you are here)</li>
|
||||
<li><a href="https://drillkea.com" target="_blank">DrillKEA.com</a></li>
|
||||
<li><a href="https://SWProBuilders.com" target="_blank">SWProBuilders.com</a></li>
|
||||
</ul>
|
||||
<br>
|
||||
<p>If you need to contact me, there are several ways:</p>
|
||||
<ul style="text-align: start;">
|
||||
<li>Email: <a href="mailto:hammer@ht-dev.de">hammer@ht-dev.de</a></li>
|
||||
<li>Matrix: <a href="https://matrix.to/#/@hammer1279:ht-dev.de" target="_blank">@hammer1279:ht-dev.de</a></li>
|
||||
<li>Matrix (Alt): <a href="https://matrix.to/#/@hammer1279:matrix.org" target="_blank">@hammer1279:matrix.org</a></li>
|
||||
<li>GitHub: <a href="https://github.com/Hammer1279" target="_blank">github.com/Hammer1279</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<footer style="text-align: center; margin-top: 20px;">
|
||||
<p>HT-Dev.DE ©2025 Hammer1279</p>
|
||||
<p>All rights reserved.</p>
|
||||
<p>Powered by <a href="https://git.ht-dev.de/Hammer1279/ht-dev.de" target="_blank">HT-Web-Framework V2</a> (to serve a static html page??)</p>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
27
public/css/style.css
Normal file
27
public/css/style.css
Normal file
@ -0,0 +1,27 @@
|
||||
@font-face {
|
||||
font-family: 'visitor1';
|
||||
src: url('/static/font/visitor1.ttf'), url('../font/visitor1.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'visitor2';
|
||||
src: url('/static/font/visitor2.ttf'), url('../font/visitor2.ttf');
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'visitor1', sans-serif;
|
||||
font-size: x-large;
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
/* text-align: center; */
|
||||
background-image: url('/static/img/stars3.gif'), url('../img/stars3.gif');
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
BIN
public/font/visitor1.ttf
Normal file
BIN
public/font/visitor1.ttf
Normal file
Binary file not shown.
BIN
public/font/visitor2.ttf
Normal file
BIN
public/font/visitor2.ttf
Normal file
Binary file not shown.
147
public/html/home-live.html
Normal file
147
public/html/home-live.html
Normal file
@ -0,0 +1,147 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <!-- Internet Explorer / Classic Edge -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- mobile device scaling stuff -->
|
||||
<title>HT-Dev DE</title>
|
||||
<!-- copied from my usual template, still here for when needed -->
|
||||
<!-- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"> -->
|
||||
<!-- <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> -->
|
||||
<meta name="title" content="HT-DEV DE">
|
||||
<meta name="description" content="HT-DEV Homepage">
|
||||
<meta name="author" content="©2025 Hammer1279">
|
||||
<meta name="robots" content="index,follow">
|
||||
<meta name="color-scheme" content="dark"> <!-- tell things like dark reader that this site is dark -->
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
<link rel="stylesheet" href="../css/style.css"> <!-- local link for development -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p style="font-size: x-small;">Logo is still WIP</p>
|
||||
<div style="max-width: 70%; margin: 0 auto; padding: 20px; border: 1px solid #ccc; border-radius: 5px;">
|
||||
<div class="center">
|
||||
<h1>HT-Dev.DE</h1>
|
||||
<h2>Hello World! Hello from Germany!</h2>
|
||||
<p>My personal effort for decentralizing and personalizing the web again.</p>
|
||||
<p>This page is in honor to the old (and free) internet.</p>
|
||||
</div>
|
||||
<br>
|
||||
<p>I'm mostly a backend developer, so please forgive this crude website.</p>
|
||||
<p>This page is still under active development and will improve hopefully.</p>
|
||||
<p>However you have found this domain, welcome! We have lots of services here:</p>
|
||||
<ul style="text-align: start;">
|
||||
<li><a href="https://git.ht-dev.de" target="_blank">Gitea</a></li>
|
||||
<li><a href="https://wiki.ht-dev.de" target="_blank">HT Wiki</a></li>
|
||||
<li><a href="https://cloud.ht-dev.de" target="_blank">Nextcloud</a></li>
|
||||
<li><a href="https://web.ht-dev.de" target="_blank">Wordpress</a></li>
|
||||
<li>Matrix Homeserver + Clients<br>
|
||||
<ul>
|
||||
<li><a href="https://chat.ht-dev.de" target="_blank">Schildichat (old)</a></li>
|
||||
<li><a href="https://cinny.ht-dev.de" target="_blank">Cinny (new)</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#" style="color: gray; pointer-events: none; text-decoration: none;">Compute (currently out of
|
||||
service)</a></li>
|
||||
<li><a href="https://mail.ht-dev.de" target="_blank">E-Mail (redirect for using @ht-dev.de accounts)</a>
|
||||
</li>
|
||||
<li><a href="telnet://ht-dev.de" style="color: gray; pointer-events: none; text-decoration: none;">Telnet
|
||||
BBS-ish Server (getting reworked)</a></li>
|
||||
</ul>
|
||||
<p>Personal Open-Source Projects to check out:</p>
|
||||
<ul style="text-align: start;">
|
||||
<li><a href="https://github.com/Hammer1279/node-proxy-manager" target="_blank">Node-Proxy-Manager</a> (this
|
||||
domain is behind this)</li>
|
||||
<li><a href="https://git.ht-dev.de/Hammer1279/node-telnet-client" target="_blank">A Node.js Telnet client
|
||||
with encryption</a> (with our own telnet server)</li>
|
||||
</ul>
|
||||
<p>Hosted Sites via our Servers:</p>
|
||||
<ul style="text-align: start;">
|
||||
<li>ht-dev.de (except this page right here)</li>
|
||||
<li><a href="https://drillkea.com" target="_blank">DrillKEA.com</a> (previously)</li>
|
||||
<li><a href="https://SWProBuilders.com" target="_blank"
|
||||
style="color: gray; pointer-events: none; text-decoration: none;">SWProBuilders.com (getting
|
||||
reworked)</a></li>
|
||||
</ul>
|
||||
<br>
|
||||
<p>If you need to contact me, there are several ways:</p>
|
||||
<ul style="text-align: start;">
|
||||
<li>Email: <a href="mailto:hammer@ht-dev.de">hammer@ht-dev.de</a></li>
|
||||
<li>Matrix: <a href="https://matrix.to/#/@hammer1279:ht-dev.de" target="_blank">@hammer1279:ht-dev.de</a>
|
||||
</li>
|
||||
<li>Matrix (Alt): <a href="https://matrix.to/#/@hammer1279:matrix.org"
|
||||
target="_blank">@hammer1279:matrix.org</a></li>
|
||||
<li>GitHub: <a href="https://github.com/Hammer1279" target="_blank">github.com/Hammer1279</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<br>
|
||||
<div style="max-width: 70%; margin: 0 auto; text-align: left;">
|
||||
<small>Badges and cool sites:</small>
|
||||
</div>
|
||||
<div id="linkback" style="margin: auto; max-width: 70%; font-size: x-small">
|
||||
<a href="http://www.mabsland.com/Adoption.html" target="_blank"><img src="http://www.mabsland.com/Pandas/Censor_PGc.gif" width="88" height="31"></a>
|
||||
<a href="https://cadence.moe/blog/2024-10-05-created-by-a-human-badges" target="_blank"><img
|
||||
src="https://cadence.moe/static/img/created-by-a-human/created-by-a-human.svg"
|
||||
alt="Created by a human with a heart" width="88" height="31"></a>
|
||||
<!-- <a href="https://ht-dev.de" target="_blank"><img src="https://ht-dev.de/static/img/button.png" width="88" height="31"></a> -->
|
||||
<iframe src="//incr.easrng.net/badge?key=htdev" style="background: url(//incr.easrng.net/bg.gif)"
|
||||
title="increment badge" width="88" height="31" frameborder="0"></iframe>
|
||||
<!-- <script type="text/javascript" src="https://www.free-counters.org/count/hp0t"></script> -->
|
||||
<!-- <script>
|
||||
// Wait for the SVG to be loaded
|
||||
setTimeout(() => {
|
||||
const svg = document.getElementById('besucherzaehler2');
|
||||
if (svg) {
|
||||
svg.setAttribute('width', '88');
|
||||
svg.setAttribute('height', '31');
|
||||
// Scale using the original aspect ratio
|
||||
svg.setAttribute('viewBox', '0 0 100 45');
|
||||
svg.style.preserveAspectRatio = 'xMinYMid meet';
|
||||
}
|
||||
}, 1000);
|
||||
</script> -->
|
||||
<img src="https://capstasher.neocities.org/88x31Buttons/css.png" width="88" height="31">
|
||||
<img src="https://retrojcities.neocities.org/files/desktopviewing.gif" width="88" height="31">
|
||||
<img src="https://capstasher.neocities.org/88x31Buttons/logoab8.gif" width="88" height="31">
|
||||
<a href="https://windows.com" target="_blank"><img src="https://capstasher.neocities.org/88x31Buttons/made_with_windows.gif" width="88" height="31"></a>
|
||||
<a href="https://code.visualstudio.com/" target="_blank"><img
|
||||
src="https://capstasher.neocities.org/88x31Buttons/vscbutton.gif" width="88" height="31"></a>
|
||||
<!-- <img src="https://capstasher.neocities.org/88x31Buttons/apple_fatal_error.gif" width="88" height="31"> -->
|
||||
<img src="https://cyber.dabamos.de/88x31/fckfb.gif" width="88" height="31">
|
||||
<img src="https://cyber.dabamos.de/88x31/fckgoogle.gif" width="88" height="31">
|
||||
<a href="https://www.mozilla.org/firefox/" target="_blank"><img src="https://cyber.dabamos.de/88x31/firefoxget.gif" width="88" height="31"></a>
|
||||
<a href="https://ublockorigin.com/"><img src="https://retrojcities.neocities.org/files/ublock-now.png"></a>
|
||||
<img src="https://retrojcities.neocities.org/files/dsbutton.gif" width="88" height="31">
|
||||
<a href="https://archive.org/" target="_blank"><img src="https://cyber.dabamos.de/88x31/internetarchive.gif" width="88" height="31"></a>
|
||||
<a href="https://cadence.moe" target="_blank"><img src="https://cadence.moe/static/img/cadence_now.png"
|
||||
alt="The text "cadence now!" on a purple background. There is a moon-shaped logo on the left side and a tiny star in the bottom right."
|
||||
width="88" height="31"></a>
|
||||
<a href="https://goblin-heart.net/sadgrl" target="_blank"><img
|
||||
src="https://goblin-heart.net/sadgrl/assets/images/buttons/sadgrlonline.gif" width="88" height="31"></a>
|
||||
<a target="_blank" href="https://melonking.net"><img alt="Visit Melonking.Net!"
|
||||
src="https://melonking.net/images/badges/MELON-BADGE-2.GIF" style="image-rendering: pixelated"></a>
|
||||
<a href="https://capstasher.neocities.org/"><img src="https://capstasher.neocities.org/csc_88x31.png" width="88"
|
||||
height="31"></a>
|
||||
</div>
|
||||
<div id="otherimg" style="margin: auto; max-width: 70%; font-size: small">
|
||||
<p>Honestly no idea how...</p>
|
||||
<a href="http://www.nerdtests.com/ft_cg.php?im"><img src="http://www.nerdtests.com/images/ft/cg.php?val=0848" alt="My computer geek score is greater than 100% of all people in the world! How do you compare? Click here to find out!"> </a>
|
||||
<script type="text/javascript" src="https://www.free-counters.org/count/hp1k"></script>
|
||||
<p style="font-size: small;">The visitor counter above usually links back to a horse insurance website and hides it and breaks if you remove it... NO!</p>
|
||||
</div>
|
||||
<!-- TODO: reenable once done editing -->
|
||||
<link href="https://melonking.net/styles/flood.css" rel="stylesheet" type="text/css" media="all" />
|
||||
<script src="https://melonking.net/scripts/flood.js"></script>
|
||||
<footer style="text-align: center; margin-top: 20px;">
|
||||
<p>HT-Dev.DE ©2025 Hammer1279</p>
|
||||
<p>All rights reserved.</p>
|
||||
<!-- <p>Powered by <a href="https://git.ht-dev.de/Hammer1279/ht-dev.de" target="_blank">HT-Web-Framework V2</a> (to serve a static html page??)</p> -->
|
||||
<!-- ^ yeah not anymore lol -->
|
||||
</footer>
|
||||
<p style="font-size: small;">Unique Visitors <span style="font-size: xx-small;">(we don't talk about its accuracy)</span></p>
|
||||
<img src="https://hitwebcounter.com/counter/counter.php?page=20392516&style=0027&nbdigits=4&type=ip&initCount=0"
|
||||
title="Counter Widget" Alt="Visit counter For Websites" border="0" width="100" height="31" />
|
||||
</body>
|
||||
|
||||
</html>
|
BIN
public/img/stars3.gif
Normal file
BIN
public/img/stars3.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Loading…
x
Reference in New Issue
Block a user