Compare commits

..

19 commits

222 changed files with 4884 additions and 3157 deletions

View file

@ -1,7 +1,7 @@
FROM alpine:edge
RUN apk update && \
apk upgrade && \
apk add nodejs yarn php8 php8-mysqli php8-mbstring php8-curl php8-simplexml git nano composer openssh-client curl gpg && \
apk add nodejs yarn php8 php8-mysqli php8-mbstring php8-curl php8-simplexml git nano composer openssh-client curl && \
ln -s /usr/bin/php8 /usr/bin/php && \
curl -L https://unpkg.com/@pnpm/self-installer | node

11
.gitignore vendored
View file

@ -1,5 +1,5 @@
#config file
public/API/lib/config.php
src/API/lib/config.php
#phpstorm
.idea/
@ -7,13 +7,14 @@ public/API/lib/config.php
# vscode
.vscode/
public/css/
public/js/
.sass-cache/
#node cache
node_modules/
pnpm-lock.yaml
#composer
public/API/vendor
src/API/vendor
# parcel
dist/
.parcel-cache/

View file

@ -1,13 +1,13 @@
docker-build:
image: gitlab.jonasled.de/jonasled/buildx-docker:latest
# Use the official docker image.
image: docker:latest
stage: build
services:
- docker:dind
before_script:
- docker context create build
- docker buildx create build --use
- docker run --rm --privileged docker/binfmt:66f9012c56a8316f9244ffd7622d7c21c1f6f28d
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
# Default branch leaves tag empty (= latest tag)
# All other branches are tagged with the escaped branch name (commit ref slug)
script:
- |
if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
@ -17,9 +17,10 @@ docker-build:
tag=":$CI_COMMIT_REF_SLUG"
echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
fi
- docker buildx build --platform linux/amd64,linux/arm,linux/arm64 --push --tag "$CI_REGISTRY_IMAGE${tag}" .
- docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
- docker push "$CI_REGISTRY_IMAGE${tag}"
# Run this job in a branch where a Dockerfile exists
rules:
- if: $CI_COMMIT_BRANCH
exists:
- Dockerfile
- Dockerfile

View file

@ -1,33 +1,26 @@
# |--------------------------------------------------------------------------
# | Build SCSS and JS
# |--------------------------------------------------------------------------
FROM docker-proxy.jonasled.de/library/node:lts-alpine AS buildJS
FROM node:lts-alpine AS buildJS
WORKDIR /build
COPY . .
RUN mkdir public/js
RUN mkdir public/css
RUN yarn install --network-timeout 1000000
RUN yarn build
RUN yarn install
RUN yarn compile
# |--------------------------------------------------------------------------
# | Install PHP dependencies
# |--------------------------------------------------------------------------
FROM docker-proxy.jonasled.de/library/composer:2 AS composer
FROM composer:2 AS composer
WORKDIR /build
COPY --from=buildJS /build .
COPY --from=buildJS /build/dist .
RUN composer install
# |--------------------------------------------------------------------------
# | Install Webserver
# |--------------------------------------------------------------------------
FROM gitlab.jonasled.de/jonasled/nginx-php-minimal:8-latest as production
ENV PHP_FILE_UPLOADS=Off \
PHP_MAX_POST=1M
FROM gitlab.jonasled.de/jonasled/nginx-php-minimal:8-latest
RUN apk update && \
apk add php8-mysqli php8-mbstring php8-curl php8-simplexml --no-cache && \
rm /etc/nginx/http.d/default.conf
COPY --from=composer /build/public .
COPY ./nginx.conf /etc/nginx/http.d/default.conf

View file

@ -1,34 +0,0 @@
# Website
This is the repo containing my personal website. It is based on simple HTML pages, with custom HTML componetes, for things like header and footer. The components are written in pure CS and compiled with webpackt to one big file. In this step also the dependencies are getting injected. Stylesheets are written in stylus and then compiled to CSS in the build process. For the backend there is a GraphQL endpoint at `/API/graphql.php` which holds most of the resources, for files (ebay images and S3) there are seperate endpoints in the API directory. Comments and the blog ist stored in a MySQL Database.
## Dev-Setup
### Requirements
* NodeJS
* Yarn
* PHP 8 with the following extensions:
* mysqli
* mbstring
* curl
* simplexml
* Composer
### Setup
1. Install the node dependencies: `yarn install`
2. Install the PHP depdendencies: `composer install`
3. Start the dev environment: `yarn watch`
This will start the compile service for the JavaScript and the stylus files and also start a PHP dev server on port 1234
## Production-Setup
For production there is a docker images with the following name available: `gitlab.jonasled.de/jonasled/website:latest`. The configuration is stored in the config PHP, a example file is available in this repo in the `/public/API/lib` folder. Later this file has to be mounted at `/var/www/html/API/lib/config.php` A example compose could look like this:
```yaml
version: '3.2'
services:
website:
image: gitlab.jonasled.de/jonasled/website:latest
restart: always
volumes:
./config.php:/var/www/html/API/lib/config.php
ports:
- "80:80"
```

View file

@ -1,8 +1,7 @@
{
"require": {
"aws/aws-sdk-php": "^3.181",
"guzzlehttp/guzzle": "^7.0",
"webonyx/graphql-php": "^14.11"
"guzzlehttp/guzzle": "^7.0"
},
"config": {
"vendor-dir": "public/API/vendor"

70
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "31a3a0321659f9c8afff63a68a9fafb6",
"content-hash": "f47a9b8d286ca72493a7fe02f263976e",
"packages": [
{
"name": "aws/aws-crt-php",
@ -669,72 +669,6 @@
}
],
"time": "2021-05-27T12:26:48+00:00"
},
{
"name": "webonyx/graphql-php",
"version": "v14.11.5",
"source": {
"type": "git",
"url": "https://github.com/webonyx/graphql-php.git",
"reference": "ffa431c0821821839370a68dab3c2597c06bf7f0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webonyx/graphql-php/zipball/ffa431c0821821839370a68dab3c2597c06bf7f0",
"reference": "ffa431c0821821839370a68dab3c2597c06bf7f0",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-mbstring": "*",
"php": "^7.1 || ^8"
},
"require-dev": {
"amphp/amp": "^2.3",
"doctrine/coding-standard": "^6.0",
"nyholm/psr7": "^1.2",
"phpbench/phpbench": "^1.2",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "0.12.82",
"phpstan/phpstan-phpunit": "0.12.18",
"phpstan/phpstan-strict-rules": "0.12.9",
"phpunit/phpunit": "^7.2 || ^8.5",
"psr/http-message": "^1.0",
"react/promise": "2.*",
"simpod/php-coveralls-mirror": "^3.0",
"squizlabs/php_codesniffer": "3.5.4"
},
"suggest": {
"psr/http-message": "To use standard GraphQL server",
"react/promise": "To leverage async resolving on React PHP platform"
},
"type": "library",
"autoload": {
"psr-4": {
"GraphQL\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A PHP port of GraphQL reference implementation",
"homepage": "https://github.com/webonyx/graphql-php",
"keywords": [
"api",
"graphql"
],
"support": {
"issues": "https://github.com/webonyx/graphql-php/issues",
"source": "https://github.com/webonyx/graphql-php/tree/v14.11.5"
},
"funding": [
{
"url": "https://opencollective.com/webonyx-graphql-php",
"type": "open_collective"
}
],
"time": "2022-01-24T11:13:31+00:00"
}
],
"packages-dev": [],
@ -745,5 +679,5 @@
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.0.0"
"plugin-api-version": "2.1.0"
}

View file

@ -1,67 +0,0 @@
class notFoundButtons extends HTMLElement {
constructor() {
super();
this.windowResized = false;
const redButton = document.createElement("div");
redButton.classList.add("button");
redButton.classList.add("red");
this.appendChild(redButton);
const yellowButton = document.createElement("div");
yellowButton.classList.add("button");
yellowButton.classList.add("yellow");
this.appendChild(yellowButton);
const greenButton = document.createElement("div");
greenButton.classList.add("button");
greenButton.classList.add("green");
this.appendChild(greenButton);
greenButton.onclick
greenButton.onclick = () => {
const terminal = document.querySelector(".terminal-window");
if (!this.windowResized) {
terminal.style.width = "95%";
terminal.style.height = "95%";
terminal.style.top = "2%";
this.windowResized = true;
} else {
terminal.style.width = "37.5rem";
terminal.style.height = "22.5rem";
terminal.style.top = "10.5rem";
this.windowResized = false;
}
}
redButton.onclick = () => {
location.href = "https://jonasled.de";
}
}
getWidth() {
return Math.max(
document.body.scrollWidth,
document.documentElement.scrollWidth,
document.body.offsetWidth,
document.documentElement.offsetWidth,
document.documentElement.clientWidth
);
}
getHeight() {
return Math.max(
document.body.scrollHeight,
document.documentElement.scrollHeight,
document.body.offsetHeight,
document.documentElement.offsetHeight,
document.documentElement.clientHeight
);
}
}
customElements.define("jl-404_buttons", notFoundButtons);

View file

@ -1,32 +0,0 @@
class blogFooter extends HTMLElement {
connectedCallback(){
this.getBlogEntries();
}
async getBlogEntries() {
let ul = document.createElement("ul");
this.appendChild(ul);
var graphql = JSON.stringify({
query: 'query($count: Int!) { blogPosts(count: $count) { title id }}',
variables: {
"count": 5
}
})
var requestOptions = {
method: 'POST',
body: graphql,
headers: { 'Content-Type': 'application/json' }
};
let posts = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.blogPosts;
posts.forEach((element) => {
let li = document.createElement("li");
let a = document.createElement("a");
a.href = "/post.html?id=" + element["id"];
a.innerText = element["title"];
li.appendChild(a);
ul.appendChild(li);
});
}
}
customElements.define("jl-footer_blog", blogFooter);

View file

@ -1,50 +0,0 @@
class BlogIndex extends HTMLElement {
constructor() {
super();
this.getBlogPosts();
}
async getBlogPosts() {
var graphql = JSON.stringify({
query: 'query($count: Int! $contentLength: Int!) { blogPosts(count: $count contentLength: $contentLength) { content title id }}',
variables: {
"count": 3,
"contentLength": 300
}
})
var requestOptions = {
method: 'POST',
body: graphql,
headers: { 'Content-Type': 'application/json' }
};
let posts = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.blogPosts;
posts.forEach((element) => {
const article = document.createElement("article");
article.classList.add("breakWord");
this.appendChild(article);
const h2 = document.createElement("h2");
h2.innerText = element["title"];
article.appendChild(h2);
const content = document.createElement("p");
content.classList.add("breakWord");
content.innerHTML = element["content"];
article.appendChild(content);
const moreP = document.createElement("p");
moreP.classList.add("center");
article.appendChild(moreP);
const moreLink = document.createElement("a");
moreLink.href = "/post.html?id=" + element["id"];
moreP.appendChild(moreLink);
const moreButton = document.createElement("button");
moreButton.innerText = "Mehr lesen";
moreLink.appendChild(moreButton);
});
}
}
customElements.define("jl-blog_index", BlogIndex);

View file

@ -1,48 +0,0 @@
class commentsDisplay extends HTMLElement {
constructor() {
super();
this.getComments()
}
async getComments() {
var graphql = JSON.stringify({
query: 'query($article: String!) { comments(article: $article) { name comment gravatarURL }}',
variables: {
"article": window.location.pathname
}
})
var requestOptions = {
method: 'POST',
body: graphql,
headers: { 'Content-Type': 'application/json' }
};
let comments = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.comments;
this.innerHTML = "";
comments.forEach((element) => {
const h3 = document.createElement("h3");
h3.classList.add("commentTitle");
h3.innerText = element["name"];
this.appendChild(h3);
const commentDiv = document.createElement("div");
commentDiv.classList.add("comment");
this.appendChild(commentDiv);
const image = document.createElement("img");
image.src = element["gravatarURL"];
commentDiv.appendChild(image);
const article = document.createElement("article");
article.classList.add("commentArticle");
commentDiv.appendChild(article);
const commentText = document.createElement("p");
commentText.classList.add("commentText");
commentText.innerText = element["comment"];
article.appendChild(commentText);
});
}
}
customElements.define("jl-comments_display", commentsDisplay);

View file

@ -1,26 +0,0 @@
class ebkBanner extends HTMLElement {
constructor(){
super();
this.generateBanner();
}
async generateBanner() {
var graphql = JSON.stringify({
query: 'query { ebayKleinanzeigen{ count }}',
})
var requestOptions = {
method: 'POST',
body: graphql,
headers: { 'Content-Type': 'application/json' }
};
let elementCount = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.ebayKleinanzeigen.count;
if(elementCount > 0) {
const h2 = document.createElement("h2");
h2.classList.add("red");
h2.innerHTML = "Ich biete aktuell verschiedene Artikel zum verkauf an, eine genaue Übersicht ist <a class=\"red\" href=\"/selling.html\">hier</a> zu sehen."
this.appendChild(h2);
}
}
}
customElements.define("jl-ebk-banner", ebkBanner);

View file

@ -1,96 +0,0 @@
class Footer extends HTMLElement {
constructor() {
super();
this.socialButtons = [
{
"link": "//www.thingiverse.com/jonasled/designs/",
"icon": "3d_model",
},
{
"link": "//paypal.me/jonasled",
"icon": "paypal",
},
{
"link": "//matrix.to/#/@jonasled:jonasled.de",
"icon": "matrix"
},
{
"link": "//twitter.com/jonasled1",
"icon": "twitter"
}
];
this.links = [
{
"name": "Datenschutzerklärung",
"link": "/datenschutzerklaerung.html"
},
{
"name": "Bildquellen",
"link": "/bildquellen.html"
},
{
"name": "Impressum",
"link": "/impressum.html"
},
{
"name": "Quellcode",
"link": "//gitlab.jonasled.de/jonasled/website"
}
]
const footer = document.createElement("footer");
this.appendChild(footer);
const blueBar = document.createElement("div");
blueBar.id = "blueBar";
footer.appendChild(blueBar);
const footerContent = document.createElement("div");
footerContent.id = "footerContent";
footer.appendChild(footerContent);
const footerLinks = document.createElement("div");
footerContent.appendChild(footerLinks);
this.links.forEach(link => {
const linkElement = document.createElement("a");
linkElement.href = link["link"];
linkElement.innerText = link["name"];
footerLinks.appendChild(linkElement);
const linebreak = document.createElement("br");
footerLinks.appendChild(linebreak);
});
const footerPostDiv = document.createElement("div");
footerPostDiv.id = "newestPost";
footerContent.appendChild(footerPostDiv);
const postHeadline = document.createElement("h3");
postHeadline.innerText = "Neueste Beiträge";
footerPostDiv.appendChild(postHeadline);
const footerPost = document.createElement("jl-footer_blog");
footerPostDiv.appendChild(footerPost);
const footerIconDiv = document.createElement("div");
footerIconDiv.className = "center";
footerContent.appendChild(footerIconDiv);
const socialButtonsContainer = document.createElement("p");
socialButtonsContainer.className = "center";
footerIconDiv.appendChild(socialButtonsContainer);
this.socialButtons.forEach(button => {
const link = document.createElement("a");
link.href = button["link"];
socialButtonsContainer.appendChild(link)
const icon = document.createElement("jl-svg");
icon.setAttribute("data-name", button["icon"]);
link.appendChild(icon);
});
}
}
customElements.define("jl-footer", Footer);

View file

@ -1,44 +0,0 @@
class Header extends HTMLElement {
constructor() {
super();
let pageTitle = this.getAttribute("data-title");
const header = document.createElement("header");
this.appendChild(header);
const headerWrapper = document.createElement("div");
headerWrapper.classList.add("header-wrapper");
header.appendChild(headerWrapper);
const headerHomepage = document.createElement("div");
headerHomepage.classList.add("header-homepage");
headerWrapper.appendChild(headerHomepage);
const alignHolder = document.createElement("div");
alignHolder.classList.add("align-holder");
headerHomepage.appendChild(alignHolder);
const h1 = document.createElement("h1");
h1.innerText = pageTitle;
h1.onclick = () => {
document.getElementById("content").scrollIntoView();
}
alignHolder.appendChild(h1);
const headerSeparator = document.createElement("div");
headerSeparator.classList.add("header-separator");
headerSeparator.classList.add("header-separator-bottom");
headerWrapper.appendChild(headerSeparator);
const svg = document.createElement("jl-svg");
svg.setAttribute("data-name", "banner");
headerSeparator.appendChild(svg);
const mainMenu = document.createElement("jl-main_menu");
mainMenu.setAttribute("data-title", pageTitle);
mainMenu.id = "mainmenu";
header.appendChild(mainMenu);
}
}
customElements.define("jl-header", Header);

View file

@ -1,48 +0,0 @@
import * as basicLightbox from 'basiclightbox'
class CustomImage extends HTMLElement {
async connectedCallback(){
const originalURL = new URL(this.getAttribute("src"), document.baseURI).href;
var graphql = JSON.stringify({
query: "query($url: String!) {imgproxy(url: $url)}",
variables: {
"url": originalURL
}
})
var requestOptions = {
method: 'POST',
body: graphql,
headers: { 'Content-Type': 'application/json' }
};
let imgproxy = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.imgproxy;
let image = document.createElement("img");
image.src = imgproxy;
if(this.getAttribute("alt") != null) image.setAttribute("alt", this.getAttribute("alt"));
if(this.getAttribute("title") != null) image.setAttribute("title", this.getAttribute("title"));
if(this.getAttribute("class") != null) image.setAttribute("class", this.getAttribute("class"));
if(this.getAttribute("style") != null) image.setAttribute("style", this.getAttribute("style"));
if(this.getAttribute("width") != null) image.setAttribute("width", this.getAttribute("width"));
if(this.getAttribute("height") != null) image.setAttribute("height", this.getAttribute("height"));
if(this.getAttribute("id") != null) image.setAttribute("id", this.getAttribute("id"));
image.setAttribute("loading", "lazy");
image.setAttribute("original-src", originalURL);
this.appendChild(image);
this.setAttribute("id", "");
if(!(this.getAttribute("data-noPreview") === "true")) {
image.onclick = () => {
const instance = basicLightbox.create(`
<img src="${originalURL}">
`);
instance.show();
}
}
}
}
customElements.define("jl-img", CustomImage)

View file

@ -1,15 +0,0 @@
class InlineCode extends HTMLElement {
constructor() {
super();
const codeElement = document.createElement("code");
if (this.hasAttribute("language")) {
codeElement.classList.add(this.getAttribute("language"));
} else {
codeElement.classList.add("language-text");
}
codeElement.innerHTML = this.innerHTML;
this.appendChild(codeElement);
}
}
customElements.define("jl-code", InlineCode);

View file

@ -1,140 +0,0 @@
class newComment extends HTMLElement {
connectedCallback() {
const buttonElement = document.createElement("button");
buttonElement.classList.add("bigButton");
buttonElement.id = "showCommentButton";
buttonElement.innerText = "Neues Kommentar verfassen";
this.appendChild(buttonElement);
buttonElement.onclick = this.setupForm;
}
async setupForm() {
var graphql = JSON.stringify({
query: "query {sitekey}"
})
var requestOptions = {
method: 'POST',
body: graphql,
headers: { 'Content-Type': 'application/json' }
};
let sitekey = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.sitekey;
let script = document.createElement('script');
script.src = "https://hCaptcha.com/1/api.js";
script.type = 'text/javascript';
script.onload = () => {
let pageName = window.location.pathname
const parent = this.parentElement;
parent.innerHTML = "";
const form = document.createElement("form");
parent.appendChild(form);
const labelName = document.createElement("label")
labelName.setAttribute("for", "name");
labelName.innerText = "Name:";
form.appendChild(labelName);
const nameInput = document.createElement("input");
nameInput.type = "text";
nameInput.name = "name";
nameInput.id = "name";
form.appendChild(nameInput);
let linebreak = document.createElement("br");
form.appendChild(linebreak);
const labelMail = document.createElement("label")
labelMail.setAttribute("for", "email");
labelMail.innerText = "E-Mail: (wird nicht veröffentlicht)";
form.appendChild(labelMail);
const mailInput = document.createElement("input");
mailInput.type = "email";
mailInput.name = "email";
mailInput.id = "email";
form.appendChild(mailInput);
linebreak = document.createElement("br");
form.appendChild(linebreak);
const labelComment = document.createElement("label")
labelComment.setAttribute("for", "comment");
labelComment.innerText = "Kommentar:";
form.appendChild(labelComment);
const commentInput = document.createElement("textarea");
commentInput.name = "comment";
commentInput.id = "comment";
form.appendChild(commentInput);
linebreak = document.createElement("br");
form.appendChild(linebreak);
const hcaptcha = document.createElement("div");
hcaptcha.classList.add("h-captcha");
hcaptcha.setAttribute("data-theme", "dark");
hcaptcha.setAttribute("data-sitekey", sitekey);
form.appendChild(hcaptcha);
linebreak = document.createElement("br");
form.appendChild(linebreak);
const submitButton = document.createElement("input");
submitButton.value = "Kommentar veröffentlichen";
submitButton.type = "submit";
form.appendChild(submitButton);
const labelDatenschutz = document.createElement("p");
labelDatenschutz.innerText = "Mit dem Klick auf den obigen Button erklären sie sich mit der ";
form.appendChild(labelDatenschutz);
const datenschutzLink = document.createElement("a");
datenschutzLink.innerText = "Datenschutzerklärung";
datenschutzLink.href = "/datenschutzerklaerung.html";
labelDatenschutz.appendChild(datenschutzLink);
const datenschutzTextNode = document.createTextNode(" einverstanden");
labelDatenschutz.appendChild(datenschutzTextNode);
submitButton.onclick = async () => {
if (nameInput.value == "" || commentInput.value == "") {
alert("Name oder Kommentar nicht ausgefüllt.");
return;
}
var graphql = JSON.stringify({
query: 'mutation($article: String!, $name: String!, $hCaptchaResponse: String!, $email: String!, $comment: String!) { comment(article: $article, name: $name, email: $email, comment: $comment, hCaptchaResponse: $hCaptchaResponse)}',
variables: {
"article": pageName,
"name": nameInput.value,
"email": mailInput.value,
"comment": commentInput.value,
"hCaptchaResponse": form.querySelector(".h-captcha iframe").getAttribute("data-hcaptcha-response")
}
})
var requestOptions = {
method: 'POST',
body: graphql,
headers: { 'Content-Type': 'application/json' }
};
let data = (await (await fetch("/API/graphql.php", requestOptions)).json()).data;
if (data.comment == "OK") {
document.querySelector("jl-comments_display").getComments();
parent.innerHTML = "<jl-new_comment></jl-new_comment>"
} else {
alert("Fehler: " + data.newComment);
}
}
form.onsubmit = () => {
return false;
}
}
document.body.append(script);
}
}
customElements.define("jl-new_comment", newComment);

View file

@ -1,120 +0,0 @@
class PasswordGenerator extends HTMLElement {
constructor() {
super();
const outValue = document.createElement("input");
outValue.type = "text";
this.appendChild(outValue);
const lineBreak = document.createElement("br");
this.appendChild(lineBreak);
const pwlen = document.createElement("input");
pwlen.type = "range";
pwlen.min = "8";
pwlen.max = "128";
pwlen.value = "32";
this.appendChild(pwlen);
const pwlenSpan = document.createElement("span");
pwlenSpan.innerText = "32";
this.appendChild(pwlenSpan);
const lineBreak2 = document.createElement("br");
this.appendChild(lineBreak2);
const includeNum = document.createElement("input");
includeNum.type = "checkbox";
includeNum.checked = true;
this.appendChild(includeNum);
const includeNumText = document.createTextNode(" Zahlen");
this.appendChild(includeNumText);
const lineBreak3 = document.createElement("br");
this.appendChild(lineBreak3);
const includeBigChar = document.createElement("input");
includeBigChar.type = "checkbox";
includeBigChar.checked = true;
this.appendChild(includeBigChar);
const bigCharText = document.createTextNode(" Großbuchstaben");
this.appendChild(bigCharText);
const lineBreak4 = document.createElement("br");
this.appendChild(lineBreak4);
const includeSmallChar = document.createElement("input");
includeSmallChar.type = "checkbox";
includeSmallChar.checked = true;
this.appendChild(includeSmallChar);
const includeSmallCharText = document.createTextNode(" Kleinbuchstaben");
this.appendChild(includeSmallCharText);
const lineBreak5 = document.createElement("br");
this.appendChild(lineBreak5);
const includeSpecialChar = document.createElement("input");
includeSpecialChar.type = "checkbox";
includeSpecialChar.checked = true;
this.appendChild(includeSpecialChar);
const inclideSpecialCharText = document.createTextNode(" Sonderzeichen");
this.appendChild(inclideSpecialCharText);
const lineBreak6 = document.createElement("br");
this.appendChild(lineBreak6);
const button = document.createElement("button");
button.innerText = "Generieren";
this.appendChild(button);
pwlen.oninput = () => {
pwlenSpan.innerText = pwlen.value;
button.click();
}
includeNum.onchange = () => {
button.click();
}
includeBigChar.onchange = () => {
button.click();
}
includeSmallChar.onchange = () => {
button.click();
}
includeSpecialChar.onchange = () => {
button.click();
}
button.onclick = () => {
let possibleChar = "";
let password = "";
if(includeNum.checked) {
possibleChar += "1234567890";
}
if(includeBigChar.checked) {
possibleChar += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
}
if(includeSmallChar.checked) {
possibleChar += "abcdefghijklmnopqrstuvwxyz";
}
if(includeSpecialChar.checked) {
possibleChar += ",;.:-_#'+*?=)(/&%$§\"!°{[]}<>|~"
}
for(let i = 0; i < pwlen.value; i++) {
password += possibleChar[Math.floor(Math.random() * possibleChar.length)];
}
outValue.value = password;
}
button.click();
}
}
customElements.define("jl-pwgen", PasswordGenerator)

View file

@ -1,106 +0,0 @@
import * as basicLightbox from 'basiclightbox'
class sellingTable extends HTMLElement {
constructor() {
super();
this.config = [
{
"title": "Bild",
"fieldName": "preview",
"displayType": "image",
"fullImage": "image",
"index": 0
},
{
"title": "Titel",
"fieldName": "title",
"displayType": "text"
},
{
"title": "Preis",
"fieldName": "price",
"displayType": "text"
},
{
"title": "Versand",
"fieldName": "shipping",
"displayType": "text"
},
{
"title": "Link",
"fieldName": "link",
"displayType": "link",
"linkText": "Anzeige ansehen",
"target": "_blank"
},
];
this.generateTable();
}
async generateTable() {
const table = document.createElement("table");
this.appendChild(table);
const tr = document.createElement("tr");
table.appendChild(tr);
this.config.forEach(element => {
const th = document.createElement("th");
th.innerText = element["title"];
tr.appendChild(th);
});
var graphql = JSON.stringify({
query: 'query { ebayKleinanzeigen(imageCount: 1) { elements { images { preview image } title price shipping link }}}',
})
var requestOptions = {
method: 'POST',
body: graphql,
headers: { 'Content-Type': 'application/json' }
};
let elements = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.ebayKleinanzeigen.elements;
elements.forEach(ad => {
const tr = document.createElement("tr");
table.appendChild(tr);
this.config.forEach(element => {
const th = document.createElement("th");
switch (element["displayType"]) {
case "text":
th.innerText = ad[element["fieldName"]];
break;
case "link":
const link = document.createElement("a");
th.appendChild(link);
link.href = ad[element["fieldName"]];
link.innerText = element["linkText"];
if ("target" in element) {
link.target = element["target"];
}
break;
case "image":
const img = document.createElement("img");
th.appendChild(img);
img.src = ad["images"][element["index"]][element["fieldName"]];
img.onclick = () => {
const instance = basicLightbox.create(`
<img src="${ad["images"][element["index"]][element["fullImage"]]}">
`);
instance.show();
}
break;
}
tr.appendChild(th);
});
});
}
}
customElements.define("jl-selling-table", sellingTable);

View file

@ -1,28 +0,0 @@
class Skill extends HTMLElement {
constructor() {
super();
this.getSkills();
}
async getSkills(){
var graphql = JSON.stringify({
query: "query {skills}"
})
var requestOptions = {
method: 'POST',
body: graphql,
headers: { 'Content-Type': 'application/json' }
};
let skills = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.skills;
skills.forEach(skill => {
const image = document.createElement("img");
image.classList.add("skills");
image.src = skill;
this.appendChild(image);
});
}
}
customElements.define("jl-skills", Skill);

View file

@ -1,45 +0,0 @@
if(window.location['pathname'] == "/post.html"){
loadPost();
}
// return the value of the get parameter with the given name
function getParameter(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)");
var results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
async function loadPost() {
let id = getParameter("id");
let header = document.createElement("jl-header");
let footer = document.createElement("jl-footer");
let content = document.createElement("div");
if(id == null) {
content.innerHTML = "<h1>404 - Post not found</h1>";
} else {
var graphql = JSON.stringify({
query: 'query($postID: String!) {blogPost(id: $postID) {content title}}',
variables: {
"postID": id
}
})
var requestOptions = {
method: 'POST',
body: graphql,
headers: { 'Content-Type': 'application/json' }
};
let post = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.blogPost;
content.innerHTML = post["content"];
document.title = post["title"] + " - Jonas Leder";
header.setAttribute("data-title", post["title"]);
}
content.id = "content";
document.body.appendChild(header);
document.body.appendChild(content);
document.body.appendChild(footer);
}

View file

@ -6,19 +6,15 @@
"author": "jonasled <git@jonasled.de>",
"license": "GPL-3.0-or-later",
"scripts": {
"build": "concurrently \"yarn css\" \"yarn js\"",
"css": "stylus styl/ -o public/css/ ",
"js": "webpack --config ./webpack.conf.js",
"watch": "concurrently \"stylus -w styl/ -o public/css/\" \"cd public && php -S 0.0.0.0:1234\" \"webpack --config ./webpack.conf.js --mode development --watch\""
"watch": "parcel serve src/*.html src/*/*.html",
"build": "parcel build src/*.html src/*/*.html"
},
"devDependencies": {
"concurrently": "^6.0.0",
"webpack": "^5.28.0",
"webpack-cli": "^4.5.0",
"stylus": "^0.56.0"
"@parcel/transformer-sass": "^2.2.1"
},
"dependencies": {
"basiclightbox": "^5.0.4",
"chart.js": "^2.9.4"
"chart.js": "^2.9.4",
"parcel": "^2.2.1"
}
}

View file

@ -1,22 +0,0 @@
<?php
if (!array_key_exists("url", $_GET) || $_GET["url"] == "") {
die("URL not set or empty");
}
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, "https://i.ebayimg.com/" . $_GET["url"]);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 20);
curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
$content = curl_exec($curl);
$httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($httpcode == 200) {
$contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
header('Content-Type: ' . $contentType);
echo ($content);
} else {
die("Failed to fetch image, server responded with " . $httpcode);
}

View file

@ -1,29 +0,0 @@
<?php
use GraphQL\Server\StandardServer;
use GraphQL\Type\Schema;
use GraphQL\Error\DebugFlag;
require 'vendor/autoload.php';
require "./lib/config.php";
require "./lib/mysql.php";
require "./queries/queries.php";
$schema = new Schema([
'query' => $queryType,
'mutation' => $mutationType,
]);
try {
$serverConfig = [
'schema' => $schema,
'rootValue' => [
'db' =>$conn,
],
'debugFlag' => DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE,
];
$server = new StandardServer($serverConfig);
$server->handleRequest();
} catch (Throwable $error) {
StandardServer::send500Error($error);
}

View file

@ -1,66 +0,0 @@
<?php
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ObjectType;
$blogPostFields = new ObjectType([
"name" => "Blog",
"fields" => [
"title" => Type::string(),
"content" => Type::string(),
"date" => Type::string(),
"id" => Type::string()
],
]);
function blogPost($id, $conn)
{
$id = $conn->real_escape_string($id);
$result = $conn->query("SELECT * FROM posts WHERE id=$id");
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
} else {
return [
"title" => "Nicht Gefunden",
"content" => "Post wurde nicht gefunden",
"date" => "2000-01-01 00:00:00",
"id" => "-1"
];
}
return [
"title" => $row["title"],
"content" => $row["content"],
"date" => $row["date"],
"id" => $row["id"],
];
}
function blogPosts($count, $contentLength, $conn)
{
$response = [];
$count = $conn->real_escape_string($count);
$result = $conn->query("SELECT * FROM posts order by id desc limit $count");
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$content = $row["content"];
if($contentLength != null && strlen($content) > $contentLength) {
$contentNew = substr($content, 0, $contentLength);
$contentRest = substr($content, $contentLength);
$content = $contentNew . explode(" ", $contentRest)[0] . " ...";
}
$blogElement = [
"title" => $row["title"],
"content" => $content,
"date" => $row["date"],
"id" => $row["id"],
];
array_push($response, $blogElement);
}
}
return $response;
}

View file

@ -1,70 +0,0 @@
<?php
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ObjectType;
use GuzzleHttp\Client;
include "lib/getGravatar.php";
$commentField = new ObjectType([
"name" => "Comment",
"fields" => [
"name" => Type::string(),
"comment" => Type::string(),
"gravatarURL" => Type::string(),
"id" => Type::int()
],
]);
function comments($article, $conn)
{
$response = [];
$article = $conn->real_escape_string($article);
$result = $conn->query("SELECT * FROM comments WHERE article='$article'");
while ($row = $result->fetch_assoc()) {
$commentElement = [
"name" => $row["name"],
"comment" => $row["comment"],
"gravatarURL" => get_gravatar($row["email"]),
"id" => $row["id"]
];
array_push($response, $commentElement);
}
return $response;
}
function newComment($conn, $article, $name, $email, $comment, $hCaptchaResponse)
{
require "./lib/config.php";
$data = array(
'secret' => $secretkey,
'response' => $hCaptchaResponse
);
$client = new Client();
$response = $client->post("https://hcaptcha.com/siteverify", [
"form_params" => $data
]);
$responseData = json_decode($response->getBody());
if (!$responseData->success) {
return "Failed to verify Captcha";
}
if(!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return "Invalid email address.";
}
$article = $conn->escape_string($article);
$name = $conn->escape_string($name);
$email = $conn->escape_string($email);
$comment = $conn->escape_string($comment);
$sql = "INSERT INTO comments (name, email, comment, article) VALUES ('$name', '$email', '$comment', '$article')";
if ($conn->query($sql) === TRUE) {
return "OK";
} else {
return "Error: " . $sql . "<br>" . $conn->error;
}
}

View file

@ -1,103 +0,0 @@
<?php
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ObjectType;
use GuzzleHttp\Client;
$ebayKleinanzeigenImages = new ObjectType([
"name" => "EBK Image",
"fields" => [
"preview" => Type::string(),
"image" => Type::string()
]
]);
$ebayKleinanzeigenElements = new ObjectType([
"name" => "EBK Elements",
"fields" => [
"title" => Type::string(),
"price" => Type::string(),
"shipping" => Type::string(),
"link" => Type::string(),
"images" => [
"type" => Type::listOf($ebayKleinanzeigenImages),
"args" => [
"count" => Type::int()
]
],
"id" => Type::string()
]
]);
$ebayKleinanzeigenFields = new ObjectType([
"name" => "Ebay Kleinanzeigen",
"fields" => [
"count" => Type::int(),
"elements" => Type::listOf($ebayKleinanzeigenElements)
],
]);
function ebayKleinanzeigen($imageCount) {
require "./lib/config.php";
$elements = [];
$client = new Client();
$headers = [
'authorization' => 'Basic ' . $ebayKleinanzeigenToken,
'user-agent' => 'okhttp/4.9.1',
'x-ebayk-app' => '4e10d7fd-6fef-4f87-afb0-b8ede2f494071636475109828',
'Host' => 'api.ebay-kleinanzeigen.de',
'Accept' => '*/*',
'Accept-Encoding' => 'gzip, deflate, br'
];
$response = $client->request('GET', "https://api.ebay-kleinanzeigen.de/api/ads.json?_in=title,price,pictures,link,features-active,search-distance,negotiation-enabled,attributes,medias,medias.media,medias.media.title,medias.media.media-link,store-id,store-title&page=0&size=31&userIds=$ebayKleinanzeigenUserId&pictureRequired=false&includeTopAds=false&limitTotalResultCount=true", [
'headers' => $headers ]);
$response = json_decode($response->getBody(), true);
$ads = $response["{http://www.ebayclassifiedsgroup.com/schema/ad/v1}ads"]["value"]["ad"];
foreach($ads as $ad) {
$element = [
"title" => html_entity_decode($ad["title"]["value"]),
"id" => $ad["id"],
"price" => $ad["price"]["amount"]["value"] . "",
"shipping" => "nein"
];
foreach($ad["attributes"]["attribute"] as $attribute) {
if(str_contains($attribute["name"], "versand")) {
$element["shipping"] = $attribute["value"][0]["value"];
}
}
foreach($ad["link"] as $link) {
if($link["rel"] == "self-public-website") {
$element["link"] = $link["href"];
}
}
$images = [];
foreach(array_slice($ad["pictures"]["picture"], 0, $imageCount) as $picture) {
$image = [];
foreach($picture["link"] as $pictureSize) {
if($pictureSize["rel"] == "teaser") {
$image["preview"] = str_replace("https://i.ebayimg.com", "/API/ebayimg.php?url=", $pictureSize["href"]);
}
if($pictureSize["rel"] == "XXL") {
$image["image"] = str_replace("https://i.ebayimg.com", "/API/ebayimg.php?url=", $pictureSize["href"]);
}
}
array_push($images, $image);
}
$element["images"] = $images;
array_push($elements, $element);
}
return [
"count" => sizeof($elements),
"elements" => $elements
];
}

View file

@ -1,10 +0,0 @@
<?php
function imgproxy($imageURL) {
require "./lib/config.php";
$encodedUrl = rtrim(strtr(base64_encode($imageURL), '+/', '-_'), '=');
$path = "/rs:fit:0:$defaultImageWidth:1/g:no/{$encodedUrl}.webp";
$signature = rtrim(strtr(base64_encode(hash_hmac('sha256', $imgProxySalt.$path, $imgProxyKey, true)), '+/', '-_'), '=');
return $imgProxyUrl . "/" . $signature . $path;
}

View file

@ -1,22 +0,0 @@
<?php
use GuzzleHttp\Client;
function mailAddress($hCaptchaResponse) {
require "./lib/config.php";
$data = array(
'secret' => $secretkey,
'response' => $hCaptchaResponse
);
$client = new Client();
$response = $client->post("https://hcaptcha.com/siteverify", [
"form_params" => $data
]);
$responseData = json_decode($response->getBody());
if($responseData->success) {
return "$contactmail";
} else {
return "Fehler beim Validieren des Captchas.";
}
}

View file

@ -1,93 +0,0 @@
<?php
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
require "./queries/skills.php";
require "./queries/blogPost.php";
require "./queries/comments.php";
require "./queries/mailAddress.php";
require "./queries/ebayKleinanzeigen.php";
require "./queries/imgproxy.php";
$queryType = new ObjectType([
'name' => 'Query',
'fields' => [
'sitekey' => [
'type' => Type::string(),
'resolve' => fn ($rootValue, $args) => $sitekey,
],
'mailAddress' => [
'type' => Type::string(),
"args" => [
"hCaptchaResponse" => Type::string()
],
'resolve' => fn ($rootValue, $args) => mailAddress($args["hCaptchaResponse"]),
],
'skills' => [
'type' => Type::listOf(Type::string()),
'resolve' => fn ($rootValue, $args) => getSkills(),
],
'blogPost' => [
"type" => $blogPostFields,
'args' => [
'id' => Type::nonNull(Type::string()),
],
'resolve' => fn ($rootValue, $args) => blogPost($args["id"], $rootValue["db"]),
],
'blogPosts' => [
"type" => Type::listOf($blogPostFields),
"args" => [
"count" => Type::nonNull(Type::int()),
"contentLength" => [
"type" => Type::int(),
"defaultValue" => null
]
],
'resolve' => fn ($rootValue, $args) => blogPosts($args["count"], $args["contentLength"], $rootValue["db"]),
],
'comments' => [
"type" => Type::listOf($commentField),
"args" => [
"article" => Type::nonNull(Type::string()),
],
'resolve' => fn ($rootValue, $args) => comments($args["article"], $rootValue["db"]),
],
'ebayKleinanzeigen' => [
"type" => $ebayKleinanzeigenFields,
"args" => [
"imageCount" => [
"type" => Type::int(),
"defaultValue" => 0
]
],
'resolve' => fn ($rootValue, $args) => ebayKleinanzeigen($args["imageCount"]),
],
'imgproxy' => [
"type" => Type::string(),
"args" => [
"url" => Type::nonNull(Type::string()),
],
'resolve' => fn ($rootValue, $args) => imgproxy($args["url"]),
]
]
]);
$mutationType = new ObjectType([
'name' => 'Mutation',
'fields' => [
"comment" => [
"type" => Type::string(),
"args" => [
"article" => Type::string(),
"name" => Type::string(),
"email" => Type::string(),
"comment" => Type::string(),
"hCaptchaResponse" => Type::string()
],
'resolve' => fn ($rootValue, $args) => newComment($rootValue["db"], $args["article"], $args["name"], $args["email"], $args["comment"], $args["hCaptchaResponse"]),
],
]
]);

View file

@ -1,40 +0,0 @@
<?php
function getSkills() {
require "./lib/config.php";
$s3Client = new Aws\S3\S3Client([
'version' => 'latest',
'region' => 'us-east-1',
'endpoint' => $S3Server,
'use_path_style_endpoint' => true,
'credentials' => [
'key' => $S3AccessKey,
'secret' => $S3SecretKey,
],
]);
$result = $s3Client->ListObjects(['Bucket' => $S3BucketName, 'Delimiter'=>'/', 'Prefix' => 'skills/']);
$response = [];
foreach ($result["Contents"] as $skill){
// Get a command object from the client
$command = $s3Client->getCommand('GetObject', [
'Bucket' => $S3BucketName,
'Key' => $skill["Key"]
]);
// Create a pre-signed URL for a request with duration of 10 miniutes
$presignedRequest = $s3Client->createPresignedRequest($command, '10 minutes');
// Get the actual presigned-url
$downloadURL = (string) $presignedRequest->getUri();
// Generate the imgproxy URL
$encodedUrl = rtrim(strtr(base64_encode($downloadURL), '+/', '-_'), '=');
$path = "/rs:fit:0:$defaultSkillsWidth:1/g:no/{$encodedUrl}.webp";
$signature = rtrim(strtr(base64_encode(hash_hmac('sha256', $imgProxySalt.$path, $imgProxyKey, true)), '+/', '-_'), '=');
array_push($response, $imgProxyUrl . "/" . $signature . $path);
}
return $response;
}

View file

@ -1,45 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title></title>
<link href="/css/style.css" rel="stylesheet">
</head>
<body>
<jl-header data-title="Über mich"></jl-header>
<div id="content">
<p>Hallo, mein Name ist Jonas Leder. Ich bin aktuell Auszubildender bei der <a
href="https://www.jobrouter.com/de/">JobRouter AG</a> in Fachrichtung Fachinformatiker für
Anwendungsentwicklung. In meiner
Freizeit beschäftige ich mich viel mit Computern und der Softwareprogrammierung. Dabei verwende ich häufig
folgende Programmiersprachen:</p>
<ul>
<li>Python</li>
<li>C / C#</li>
<li>"HTML"</li>
<li>JavaScript / TypeScript</li>
<li>CSS / SCSS</li>
<li>PHP</li>
<li>Java</li>
<li>Bash</li>
</ul>
<p>Die oben genannten Sprachen setzt ich auf PCs und Microcontrollern ein. Dabei verwende ich die Standard
Arduino
Boards (nano, mega, …) und spezielle, wie das ESP8266, welches WLAN unterstützt.</p>
<p>Neben Microcontrollern beschäftige ich auch viel mit Servern und Computern. Auch für diese Systeme schreibe
ich verschiedenste Programme und Scripts. Mein Hauptgebiet ist aktuell die Webentwicklung. Dierfür setze ich
bei den meisten Seiten auf HTML, JS und SCSS für das Frontend. Im Backend nutze ich in den meisten Fällen
PHP.</p>
<p>
Neben der Erfahrung im Programmieren kenne ich mich auch sowohl mit Windows als auch mit Linux aus. Wobei
ich mittlerweile fast überall nurnoch Linux einsetze und Windows nur im Ausnahmefall verwendet wird.
</p>
</div>
<jl-footer></jl-footer>
<script src="/js/script.js"></script>
</body>
</html>

View file

@ -1,184 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/css/style.css" rel="stylesheet">
<title></title>
</head>
<body>
<jl-header data-title="Traefik Reverse Proxy"></jl-header>
<div id="content">
<p>In der folgenden Anleitung werde ich darauf eingehen, wie man eine VM mit alpine Linux aufsetzt, darauf
Docker installiert und dann als Container einen Traefik 2 reverse Proxy.</p>
<p>Als erstes muss eine neue VM erstellt werden und als Boot Medium Alpine Linux eingelegt werden. Wenn die ISO
gebootet ist kommt ein Login Fenster, hier einfach mit dem Benutzer <code class="language-text">root</code>
anmelden. Danach sollte ein Terminal Prompt kommen. Um Alpine zu installieren muss der Befehl <code
class="language-text">setup-alpine</code> eingegeben werden. Hierbei gilt es zu beachten, dass die ISO
ein englisches Tastaturlayout benutzt. Der Bindestrich liegt hier auf dem ß. Als erstes muss nun im Setup
das Tastaturlayout angegeben werden, um dieses auf Deutsch festzulegen zwie mal <code
class="language-text">de</code> eingeben.
Nachdem die Tastatur geändert wurde muss der Hostname festgelegt werden, meine VM heißt einfach <code
class="language-text">docker</code>. In der darauffolgenden Netzwerkkonfiguration muss als erstes der
Name des zu konfigurierendes Netzwerkinterfaces angegeben werden (meist <code
class="language-text">eth0</code>)
Im darauffolgenden Schritt wird die Methode der IP konfiguration angegeben. Im Folgenden setze ich hier auf
DHCP. Nachdem das root Passwort (welches mit einem <a href="/passwordgen.html">Passwortgenerator</a>
generiert wurde) festgelegt wurde muss die Zeitzone angegeben werden. Für Deutschland ist
diese <code class="language-text">Europe/Berlin</code>. Einen Proxy brauchen wir nicht, genauso wie beim
mirror können wir einfach mit enter bestätigen. Der SSH Server kann auch bei openssh belassen werden.
Nachdem nun die Grundkonfiguration im Installer abgeschlossen ist, muss noch die Festplatte angegeben
werden. Dafür wird eine Liste an erkannten Platten angezeigt. In meinem Fall war dies <code
class="language-text">sda</code> und danach noch der Typ. Dies ist <code
class="language-text">sys</code>,
da das System auf die Platte installiert wird. Nach der Installation muss das System nochmal neugestartet
werden.
</p>
<p>
Wenn das System nun von der Festplatte gebootet hat kann man sich mit dem Benutzer <code
class="language-text">root</code> und dem zuvor gewählten Passwort anmelden. Danach muss als erstes das
System auf den neuesten Stand gebracht werden und ein paar tool installiert werden. Dazu die beiden unten
ausfgeführten Befehle eingeben.
<pre>
<code class="language-bash">apk update
apk upgrade
apk add nano htop git</code>
</pre>
Um nun Docker zu installieren muss als erstes die Community repo aktiviert werden. Dazu mit <code
class="language-bash">nano</code> die Datei <code class="language-text">/etc/apk/repositories</code> öffnen
und in der Zeile, welche mit <code class="language-text">community</code> endet das <code
class="language-text">#</code> am Anfang entfernen. (Nicht in den Zeilen mit <code
class="language-text">edge</code> im URL) Danach kann Docker installiert werden.
</p>
<pre>
<code class="language-bash">nano /etc/apk/repositories
apk update
apk add docker docker-compose
rc-update add docker
/etc/init.d/docker start</code>
</pre>
<p>
Nun ist Docker auf unserem System installiert und kann eingesetzt werden. Um Traefik 2 nun einzusetzen muss
als erstes meine Vorlage von <a href="https://gitlab.jonasled.de/jonasled/traefik-config">hier</a>
heruntergeladen werden. Danach muss in der Datei <code class="language-text">config/traefik.yml</code> unter
letsencrypt => acme => email die E-Mail Adresse festgelegt werden, welche für letsencrypt verwendet werden
soll. Danach noch die Berechtigungen von der Zertifikatsdatei einschränken. Bevor wir traefik starten könenn
müssen wir noch ein Netzwerk namens <code class="language-text">web</code> angelegt werden. Nachdem nun
alles vorbereit wurde kann dieser mit
<code class="language-bash">docker-compose up</code> gestartet werden.
</p>
<pre>
<code class="language-bash">git clone https://gitlab.jonasled.de/jonasled/traefik-config
cd traefik-config
nano config/traefik.yml
chmod 600 letsencrypt/acme.json
docker network create web
docker-compose up
# Wenn alles läuft strg und c drücken
docker-compose up -d</code>
</pre>
<p>
Traefik ist nun installiert und sollte von außen erreichbar sein. Als Antwort sollte bei nicht bekannten
Domains immer ein 404 Fehler kommen. Zum testen setzen wir als nächstes den whoami Docker Container auf,
dieser ist nur wenige kb groß und bietet einen minimalen Webserver. Dazu muss als erstes die unten
angehängte docker-compose auf dem Host in einem neuen Ordner unter dem Namen <code
class="language-text">docker-compose.yml</code> abspeichern und den Host anpassen. Danach kann der
Container mit <code class="language-bash">docker-compose up</code> gestartet werden. Nun sollte nach 1-2
Minuten auf dem zuvor angegebenen Domain die 404 Meldung durch eine Seite ersetzt werden. Falls dies nicht
der Fall ist kann im Ordner, in dem der Traefik abgelegt wurde der Befehl <code
class="language-text">docker-compose logs -f</code> ausgeführt werden um den Fehlerlog zu überprüfen.
</p>
<pre>
<code class="language-yaml">version: "3.2"
services:
whoami:
image: containous/whoami
restart: unless-stopped
networks:
- web
labels:
- traefik.http.routers.whoami-https.rule=Host(`whoami.jonasled-test.xyz`)
- traefik.http.routers.whoami-https.entrypoints=https
- traefik.http.routers.whoami-https.tls=true
- traefik.http.routers.whoami-https.tls.certresolver=letsencrypt
- traefik.http.services.whoami.loadbalancer.server.port=80
networks:
web:
external: true</code>
</pre>
<p>
Um die Konfigurationen für den Traefik Server zu erstellen verwende nutze ich ein kleines selber
geschriebenes Tool, welches <a
href="https://jonasled.pages.gitlab.jonasled.de/traefik-config-generator/">hier</a> erreichbar ist.
</p>
<h2>Verbesserungen</h2>
<h3>HTTP auf HTTPS weiterleiten</h3>
<p>Ich empfehle diesen Schritt für alle, da dadurch traefik den Nutzer automatisch von einer unverschlüsselten
HTTP Verbindung auf eine verschlüsselte HTTPS verbindung weiterleitet. Dies verhindert das mitlesen der
Daten durch dritte. Um die Weiterleitung einzurichten muss unter <code
class="language-text">config/providers</code> eine neue Datei mit der Endung <code
class="language-text">.yml</code> angelegt werden. (also z.B. <code
class="language-bash">nano config/providers/http.yml</code>) Danach muss in die Datei der unten
angeführte Snippet eingefügt werden und danach gescpeichert werden (strg + x und dann mit y und enter
bestätigen)
</p>
<pre>
<code class="language-yaml">http:
routers:
http-redirect:
rule: HostRegexp(`{any:.+}`)
middlewares: redirect
service: dummy
middlewares:
redirect:
redirectscheme:
scheme: https
services:
dummy:
loadBalancer:
servers:
- url: "http://0.0.0.0/"
passHostHeader: true</code>
</pre>
<h2>Nicht Docker Services einbinden</h2>
<p>
Das Einbinden von Diensten, die nicht auf dem Docker Host laufen ist auch ziemlich einfach. Dafür muss nur
eine Provider yaml Datei (Dateiendung .yml) im <code class="language-text">config/providers</code> Ordner
mit dem nachfolgenden Inhalt angelegt werden. Um den Router zu nutzen muss <code
class="language-text">servicename</code> und <code class="language-text">routername</code> durch Namen
ersetzt werden. Zusätzlich muss im Router der Hostname und im Service der interne URL zu dem dienst, der von
außen erreichbar sein soll gesetzt werden.
</p>
<pre>
<code class="language-yaml">http:
# Add the router
routers:
routername:
service: servicename
rule: Host(`whoami.jonasled-test.xyz`)
tls:
certresolver: letsencrypt
# Add the service
services:
servicename:
loadBalancer:
servers:
- url: http://10.0.0.1
passHostHeader: true</code>
</pre>
<h2>Kommentare:</h2>
<jl-comments_display></jl-comments_display>
<jl-new_comment id="newComment"></jl-new_comment>
</div>
<jl-footer></jl-footer>
<script src="/js/script.js"></script>
</body>
</html>

View file

@ -1,79 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/css/style.css" rel="stylesheet">
<title></title>
</head>
<body>
<jl-header data-title="Vaultwarden Passwortmanager"></jl-header>
<div id="content">
<p>
Vaultwarden ist ein Passwort Manager, welcher das Frontend und die Anwendungen von Bitwarden unterstützt,
aber durch die Implementation in Rust schneller und Resourcensparender als der originale Server ist.
Zusätzlich bietet Vaultwarden alle Premium Features kostenlos. Für die Installation setze ich Vorraus, dass
auf dem System bereits ein Reverse Proxy wie <a href="traefik.html">Traefik 2</a> existiert und Docker mit
Docker Compose installiert ist.
</p>
<p>
Als erstes muss ein neuer Ordner auf dem Host erstellt werden. In diesem muss als erstes eine <jl-code>
docker-compose.yml</jl-code> Datei mit dem Inhalt unten erstellt werden.
<pre>
<code class="language-yaml">version: "3.2"
services:
bitwarden:
image: vaultwarden/server:latest
restart: always
ports:
- 8080:80
- 3012:3012
environment:
- ADMIN_TOKEN=
- DOMAIN=https://bitwarden.jonasled-test.xyz
- WEBSOCKET_ENABLED=true
volumes:
- ./data:/data</code>
</pre>
In der Datei muss noch der <jl-code>ADMIN_TOKEN</jl-code> auf ein sicheres Passwort aus einem <a
href="/passwordgen.html">Passwortgenerator</a> ersetzt werden. Dieses Password wird benötigt um die Admin
Oberfläsche zu erreichen. Da über die Oberfläche auch zum Beispiel Nutzerkonten gelöscht werden können sollte es
wirklich sehr sicher sein. Zusätzlich muss auch der URL angegeben werden, über den der Passwortmanager später
von außen erreichbar ist.
</p>
<p>
Als nächstes muss der Reverse Proxy eingerichtet werden. Dazu muss Port 8080 als root Pfad (also /) und auf
<jl-code>/notifications/hub</jl-code> port 3012 freigegeben werden. Traefik Nutzer können die Docker Compose
welche <a href="https://gitlab.jonasled.de/-/snippets/8">hier</a> zu finden ist anstelle der obigen
verwenden und dort die Hosts anpassen.
</p>
<p>
Nachdem nun die Konfiguration abgeschlossen ist kann der Container mit dem Befehl <jl-code>docker-compose up
</jl-code> das erste mal gestartet werden. Wenn der Container läuft und alles funktioniert kann der
Container wieder mit strg und c gestoppt werden und danach mit <jl-code>docker-compose up -d</jl-code> im
Hintergrund gestartet werden.
</p>
<p>
Als erstes sollte das Admin Interface aufgerufen werden und ein SMTP Server für den E-Mail versand
eingestellt werden. Um das Interface zu erreichen muss an den URL <jl-code>/admin</jl-code> angehängt und
dass Passwort aus der <jl-code>docker-compose.yml</jl-code> angegeben werden.
</p>
<p>
Nun sollte die Konfiguration vom Bitwarden / Vaultwarden abgeschlossen sein. Wenn man jetzt die URL aufruft
kann man einen neuen Account anlegen. Im Client muss man zuerst in die Einstellungen gehen und dann dort den
URL zum Server angeben. Ganz wichtig ist hierbei das vorgestellte HTTPS. Anschließend funktioniert der Login
auf der Startseite mit dem gleichen Benutzer, wie auch im Webclient.
</p>
<jl-img src="/img/anleitungen/bitwarden/bitwarden_browser_1.jpg"></jl-img>
<jl-img src="/img/anleitungen/bitwarden/bitwarden_browser_2.jpg"></jl-img>
<h2>Kommentare:</h2>
<jl-comments_display></jl-comments_display>
<jl-new_comment id="newComment"></jl-new_comment>
</div>
<jl-footer></jl-footer>
<script src="/js/script.js"></script>
</body>
</html>

View file

@ -1,65 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
0Kfe8lA=
=cYJ8
-----END PGP PUBLIC KEY BLOCK-----

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View file

@ -1,45 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<link href="/css/style.css" rel="stylesheet">
<title></title>
</head>
<body>
<jl-header data-title="Jonas Leder"></jl-header>
<div id="content">
<div class="center">
<jl-img data-noPreview="true" id="homeImage" src="/img/bildHome.webp"></jl-img>
<h2>&Uuml;ber mich</h2>
<p class="left block">Hallo, mein Name ist Jonas Leder. Ich bin aktuell Auszubildender in Fachrichtung
Fachinformatiker für Anwendungsentwicklung.</p>
<p>
<button onclick="window.location.href='about.html';">Mehr</button>
</p>
<div class="spacer"></div>
<h2>Blog</h2>
<jl-blog_index id="blog"></jl-blog_index>
<jl-ebk-banner></jl-ebk-banner>
<h2>Skills</h2>
<jl-skills></jl-skills>
<div class="banner">
<h2>Banner</h2>
<a href="https://ipv6.he.net" target="_blank"><img class="no-corner" data-noPreview="true"
src="https://ipv6.he.net/certification/create_badge.php?pass_name=jonasled&badge=3"></a>
<a href="https://www.abuseipdb.com/user/45130" target="_blank"><img class="abuseIpImg no-corner"
data-noPreview="true" src="https://www.abuseipdb.com/contributor/45130.svg"></a>
</div>
<br>
<br>
</div>
</div>
<jl-footer></jl-footer>
<script src="/js/script.js"></script>
</body>
</html>

View file

@ -1,43 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title></title>
<link href="/css/style.css" rel="stylesheet">
</head>
<body>
<jl-header data-title="Proxdroid"></jl-header>
<div id="content">
<p>Ich setze schon seit einer ganzen weile auf meinem Homeserver auf Proxmox, leider gab es bisher für Android
nur
einen Client, dieser ist aber Kostenpflichtig, daher habe ich mir gedacht, wie wäre es mit einer kostenlosen
Alternative, die besser ist. Was bei meinen Projekten natürlich auch wichtig ist, ist das veröffentlichen
des
Quellcodes, dass jeder helfen / seine eigene Version bereitstellen kann. Dieses mal habe ich aber nicht auf
meine eigene Gitlab Instanz, sondern auf die öffentliche gesetzt, da ich dort die schnelleren Runner zur
Verfügung habe. Die repo ist unter folgendem Link erreichbar: <a
href="https://gitlab.com/jonasled/proxdroid">gitlab.com/jonasled/proxdroid</a>
</p>
<p>Natürlich habe ich die App auch im <a
href="https://play.google.com/store/apps/details?id=de.jonasled.proxdroid">PlayStore</a>
veröffentlicht, um sie so einfach wie möglich herunterladen zu können. Für die, die den PlayStore meiden
wollen
gibt es aber auch die <a href="https://gitlab.com/jonasled/proxdroid/-/releases">Releases</a> direkt auf
GitLab.
Um neue Features einzureichen oder sich untereinander zu helfen habe ich daneben noch einen <a
href="https://discord.gg/JEuXW8h">Discord</a> eingerichtet.</p><br>
<jl-img src="/img/projekte/proxdroid/screen_1.png"></jl-img>
<jl-img src="/img/projekte/proxdroid/screen_2.png"></jl-img>
<jl-img src="/img/projekte/proxdroid/screen_3.png"></jl-img>
<jl-img src="/img/projekte/proxdroid/screen_4.png"></jl-img>
<h2>Kommentare:</h2>
<jl-comments_display></jl-comments_display>
<jl-new_comment id="newComment"></jl-new_comment>
</div>
<jl-footer></jl-footer>
<script src="/js/script.js"></script>

View file

@ -1 +0,0 @@
OpenBugBounty: https://openbugbounty.org/bugbounty/jonasled1/

View file

@ -1 +0,0 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC5lj9tx2xeEl11qbPTQJIcSOEQPHIPUurCKWSZFyBwsJIeef3HzNcyP7yvDFGuGiHP7NIRyRzvp4hkg537ucfFk+ZqpLdaH+mwKV5zdwl5J95WnfkzBEZ0GOLMFdGnblgwpz37dlZDEZo1BvxqJkapM9BPzWfuiMhwokbNcCJLTBopFQmUqGgyo6wDDCkVU+fTFmK9LgJxRG0RNGgpJGQtLu4kLvTt04WRsMnvrLivlcFNp94RClb8CbfQmi5oBMR34zzTKew3p78Dv1cxWRMrseCWoBipcg607pPCEOaSKNlPn+mk2Um7+bDMb+0F0pAgF0y0FVFH2ZAQpRGipFUA1sburicOwg/QnZRj3TdTYDV/XApvxeBACy7CicJmjqwKb0TnK/y5YRgU5c+aEYX+TeC4WrIIks8LEwtEY5qB2JDQdoc4zXuR/GMbgnCx4xUsqlm3U7km8wewTjM9dM0EjDsUSmy3ocNKSHvcMFy6+2KZiZRaS/JdvOUPTeKj+fM= jonasled@PC

View file

@ -3,12 +3,16 @@
<head>
<meta charset="UTF-8">
<title>404 - Page not found</title>
<link href="/css/error.css" rel="stylesheet">
<link href="scss/error.scss" rel="stylesheet">
</head>
<body style="height: 100vh;">
<body>
<jl-matomo></jl-matomo>
<div id="particles-js"></div>
<div class="terminal-window">
<header>
<jl-404_buttons></jl-404_buttons>
<div class="button green"></div>
<div class="button yellow"></div>
<div class="button red"></div>
</header>
<section class="terminal">
<div class="history"></div>
@ -39,6 +43,6 @@
];
</script>
<script src="/js/script.js"></script>
<script src="js/script.js" type="module"></script>
</body>
</html>

12
src/API/config.php Normal file
View file

@ -0,0 +1,12 @@
<?php
require "./lib/config.php";
$configValue = $_GET['name'];
switch ($configValue){
case "sitekey":
echo($sitekey);
break;
default:
echo("notFound");
}

12
src/API/ebayimg.php Normal file
View file

@ -0,0 +1,12 @@
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, "https://i.ebayimg.com/" . $_GET["url"]);
curl_setopt ($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt ($curl, CURLOPT_CONNECTTIMEOUT, 20);
curl_setopt ($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
curl_setopt ($curl, CURLOPT_FOLLOWLOCATION, true);
$content = curl_exec ($curl);
$contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
header('Content-Type: ' . $contentType);
echo($content);

55
src/API/ebk.php Normal file
View file

@ -0,0 +1,55 @@
<?php
require 'vendor/autoload.php';
require "./lib/config.php";
use GuzzleHttp\Client;
$responseJSON = [];
$client = new Client();
$headers = [
'authorization' => 'Basic ' . $ebayKleinanzeigenToken,
'user-agent' => 'okhttp/4.9.1',
'x-ebayk-app' => '4e10d7fd-6fef-4f87-afb0-b8ede2f494071636475109828',
'Host' => 'api.ebay-kleinanzeigen.de',
'Accept' => '*/*',
'Accept-Encoding' => 'gzip, deflate, br'
];
$response = $client->request('GET', "https://api.ebay-kleinanzeigen.de/api/ads.json?_in=title,price,pictures,link,features-active,search-distance,negotiation-enabled,attributes,medias,medias.media,medias.media.title,medias.media.media-link,store-id,store-title&page=0&size=31&userIds=$ebayKleinanzeigenUserId&pictureRequired=false&includeTopAds=false&limitTotalResultCount=true", [
'headers' => $headers ]);
$response = json_decode($response->getBody(), true);
$ads = $response["{http://www.ebayclassifiedsgroup.com/schema/ad/v1}ads"]["value"]["ad"];
foreach($ads as $ad) {
$element = [
"title" => $ad["title"]["value"],
"price" => $ad["price"]["amount"]["value"] . ""
];
foreach($ad["link"] as $link) {
if($link["rel"] == "self-public-website") {
$element["link"] = $link["href"];
}
}
if(sizeof($ad["pictures"]["picture"]) > 0) {
foreach($ad["pictures"]["picture"][0]["link"] as $picture) {
if($picture["rel"] == "teaser") {
$element["previewImage"] = str_replace("https://i.ebayimg.com", "/API/ebayimg.php?url=", $picture["href"]);
}
if($picture["rel"] == "XXL") {
$element["image"] = str_replace("https://i.ebayimg.com", "/API/ebayimg.php?url=", $picture["href"]);
}
}
}
array_push($responseJSON, $element);
}
if(isset($_GET["count"])) {
echo sizeof($responseJSON);
die();
}
echo json_encode($responseJSON);

View file

@ -0,0 +1,29 @@
<?php
include "./lib/config.php";
include "./lib/mysql.php";
$position = $_GET['position'];
if($position == "index"){
$limit = $homeMaxPost;
} else if($position == "footer"){
$limit = $footerMaxPost;
} else {
die("wrong parameter");
}
$responseJSON = [];
$result = $conn->query("SELECT * FROM posts order by id desc limit $limit");
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$blogElement = [
"title" => $row["title"],
"id" => $row["id"],
"content" => $row["content"]
];
array_push($responseJSON, $blogElement);
}
}
header('Content-Type: application/json');
echo json_encode($responseJSON);

22
src/API/getMail.php Normal file
View file

@ -0,0 +1,22 @@
<?php
include("./lib/config.php");
require("./vendor/autoload.php");
use GuzzleHttp\Client;
$data = array(
'secret' => $secretkey,
'response' => $_POST['h-captcha-response']
);
$client = new Client();
$response = $client->post("https://hcaptcha.com/siteverify", [
"form_params" => $data
]);
$responseData = json_decode($response->getBody());
if($responseData->success) {
echo("$contactmail");
} else {
echo("Failed to verify Captcha");
}

24
src/API/getPost.php Normal file
View file

@ -0,0 +1,24 @@
<?php
include "./lib/config.php";
include "./lib/mysql.php";
$id = $conn->real_escape_string($_GET["id"]);
$result = $conn->query("SELECT * FROM posts WHERE id=$id");
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
} else {
die("Post not found");
}
$title = $row["title"];
$content = $row["content"];
$date = $row["date"];
$id = $row["id"];
header('Content-Type: application/json');
echo json_encode([
"title" => $title,
"content" => $content,
"date" => $date,
"id" => $id
]);

View file

@ -19,10 +19,4 @@ $S3SecretKey = "";
$S3BucketName = "";
$ebayKleinanzeigenUserId = "";
$ebayKleinanzeigenToken = ""; # To get this token you have to sniff the HTTPS traffic from the app or decompile the app and find it.
$imgProxyUrl = "";
$imgProxyKey = "";
$imgProxySalt = "";
$defaultImageWidth = 512; # width is in px
$defaultSkillsWidth = 80; # width is in px
$ebayKleinanzeigenToken = ""; # To get this token you have to sniff the HTTPS traffic from the app or decompile the app and find it.

View file

@ -97,16 +97,6 @@
"name": "Mailcow E-Mail Server",
"url": "/anleitungen/mailcow.html",
"type": "link"
},
{
"name": "Traefik 2 Reverse Proxy",
"url": "/anleitungen/traefik.html",
"type": "link"
},
{
"name": "Vaultwarden Passwort Manager",
"url": "/anleitungen/vaultwarden.html",
"type": "link"
}
]
},
@ -124,6 +114,11 @@
"url": "/systeme/laptop.html",
"type": "link"
},
{
"name": "NAS",
"url": "/systeme/nas.html",
"type": "link"
},
{
"name": "Epyc Server",
"url": "/systeme/epycServer.html",
@ -133,6 +128,11 @@
"name": "i7 Server",
"url": "/systeme/i7Server.html",
"type": "link"
},
{
"name": "Backup Server",
"url": "/systeme/backupServer.html",
"type": "link"
}
]
},
@ -179,11 +179,6 @@
"name": "Passwort Generator",
"url": "/passwordgen.html",
"type": "link"
},
{
"name": "Speedtest Server",
"url": "//jonasled.speedtestcustom.com",
"type": "link"
}
]
},
@ -196,6 +191,11 @@
"url": "//status.jonasled.de",
"type": "link"
},
{
"name": "Discord Bots",
"url": "//discordstatus.jonasled.de",
"type": "link"
},
{
"name": "NTP Server",
"url": "/ntpstatus.html",

39
src/API/newComment.php Normal file
View file

@ -0,0 +1,39 @@
<?php
require './vendor/autoload.php';
include("./lib/config.php");
include("./lib/mysql.php");
use GuzzleHttp\Client;
$data = array(
'secret' => $secretkey,
'response' => $_POST['h-captcha-response']
);
$client = new Client();
$response = $client->post("https://hcaptcha.com/siteverify", [
"form_params" => $data
]);
$responseData = json_decode($response->getBody());
if($responseData->success) {
$article =$conn->escape_string($_POST["pagename"]);
$name = $conn->escape_string($_POST["name"]);
$email = $conn->escape_string($_POST["email"]);
$comment = $conn->escape_string($_POST["comment"]);
$sql = "INSERT INTO comments (name, email, comment, article) VALUES ('$name', '$email', '$comment', '$article')";
if ($conn->query($sql) === TRUE) {
header("Location: " . $_SERVER["HTTP_REFERER"]);
} else {
echo "Error: " . $sql . "<br>" . $conn->error;
}
} else {
echo "Failed to verify captcha.";
}

View file

@ -0,0 +1,23 @@
<?php
include "./lib/config.php";
include "./lib/mysql.php";
include "./lib/getGravatar.php";
$article = $conn->real_escape_string($_GET["article"]);
$responseJSON = [];
$result = $conn->query("SELECT * FROM comments WHERE article='$article'");
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$commentElement = [
"name" => $row["name"],
"comment" => $row["comment"],
"gravatarURL" => get_gravatar($row["email"])
];
array_push($responseJSON, $commentElement);
}
}
header('Content-Type: application/json');
echo json_encode($responseJSON);

24
src/API/skills.php Normal file
View file

@ -0,0 +1,24 @@
<?php
include("./lib/config.php");
require 'vendor/autoload.php';
$s3Client = new Aws\S3\S3Client([
'version' => 'latest',
'region' => 'us-east-1',
'endpoint' => $S3Server,
'use_path_style_endpoint' => true,
'credentials' => [
'key' => $S3AccessKey,
'secret' => $S3SecretKey,
],
]);
$result = $s3Client->ListObjects(['Bucket' => $S3BucketName, 'Delimiter'=>'/', 'Prefix' => 'skills/']);
$response = [];
foreach ($result["Contents"] as $skill){
array_push($response, $skill["Key"]);
}
header("Content-Type: application/json");
echo json_encode($response);

35
src/about.html Normal file
View file

@ -0,0 +1,35 @@
<!DOCTYPE html >
<html lang="de">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title></title>
<link href="scss/style.scss" rel="stylesheet">
</head>
<body>
<jl-header data-title="Über mich"></jl-header>
<div id="content">
<p>Hallo, mein Name ist Jonas Leder. Ich bin aktuell Auszubildender bei der <a href="https://www.jobrouter.com/de/">JobRouter AG</a> in Fachrichtung Fachinformatiker für Anwendungsentwicklung. In meiner
Freizeit beschäftige ich mich viel mit Computern und der Softwareprogrammierung. Dabei verwende ich häufig
folgende Programmiersprachen:</p>
<ul>
<li>Python</li>
<li>C / C#</li>
<li>"HTML"</li>
<li>JavaScript / TypeScript</li>
<li>CSS / SCSS</li>
<li>PHP</li>
<li>Java</li>
<li>Bash</li>
</ul>
<p>Die oben genannten Sprachen setzt ich auf PCs und Microcontrollern ein. Dabei verwende ich die Standard Arduino
Boards (nano, mega, …) und spezielle, wie das ESP8266, welches WLAN unterstützt.</p>
<p>Daneben habe ich auch viel Erfahrung mit Betriebssystemen. So setzt ich auf meinem Server auf Ubuntu und benutze
auch viel Windows Server.</p>
<p>Meine Programme veröffentliche ich meisten auf meiner GitLab Instanz. Diese ist unter&nbsp;<a
href="https://gitlab.jonasled.de/">gitlab.jonasled.de</a>&nbsp;zu finden.</p>
</div>
<jl-footer></jl-footer>
<script src="js/script.js" type="module"></script>
</body>
</html>

View file

@ -5,7 +5,7 @@
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title></title>
<link href="/css/style.css" rel="stylesheet">
<link href="../scss/style.scss" rel="stylesheet">
</head>
<body>
@ -18,16 +18,16 @@
href="https://www.debian.org/download">hier</a> heruntergeladen werden. Hierfür wird ein System mit
mindestens 2GB RAM, 2 Kernen und 10GB Storage empfohlen. Wenn die VM con der CD gebootet wird kommt als
erstes ein Auswahlmenü, in diesem die Option Install wählen und danach enter drücken.</p>
<jl-img src="/img/anleitungen/mailcow/debian_grub.jpg"></jl-img>
<img src="../assets/img/debian_grub.jpg">
<p>Im nächsten Schritt wird die Sprache, die Region und das Tastaturlayout fetgelegt. Im nächsten Schritt
konfiguriert Debian einige Einstellungen wie die Netzwerkkonfiguration. Wenn die automatische Konfiguration
abgeschlossen ist, frag der Installer nach dem Hostname, dieser kann frei gewählt werden. Ich verwende
hierbei gerne Namen, die zu dem System passen, wie zum Beispiel mailcow. Der Domain Name im darauffolgenden
Schritt kann leer gelassen werden. Wenn dieser festgelegt wurde sollte das Passwort für den root Benutzer
festgelegt werden. Hierbei sollte auf einen <a href="/passwordgen.html/">Passwortgenerator</a>
festgelegt werden. Hierbei sollte auf einen <a href="../passwordgen.html">Passwortgenerator</a>
gesetzt werden. Nachdem das Passwort für den root Benutzer festgelegt wurde fragt Debian noch nach
benötigten Daten für einen nicht root Nutzer. Hierbei muss ein Anzeigenahme, ein Nutzername und ein <a
href="/passwordgen.html/">generiertes Passwort</a> festgelegt werden. Die Partitionierung
href="../passwordgen.html">generiertes Passwort</a> festgelegt werden. Die Partitionierung
wird mit <code class="language-text">Guieded - use entire disk</code> bestätigt, danach die Festplatte
ausgewählt. Als Partitionsschema wird
<code class="language-text">All Files in one partition</code> gewählt. Wenn alle Optionen gesetzt wurden
@ -37,8 +37,8 @@
class="language-text">yes</code>
bestätigt.
</p>
<jl-img src="/img/anleitungen/mailcow/debian_partition_method.jpg"></jl-img>
<jl-img src="/img/anleitungen/mailcow/debian_partition_finish.jpg"></jl-img>
<img src="../assets/img/debian_partition_method.jpg">
<img src="../assets/img/debian_partition_finish.jpg">
<p>Nun wird Debian auf die Festplatte installiert. Jenachdem wie schnell das Bootlaufwerk und die Festplatte ist
kann dieser Schritt einige Minuten dauern. Nachdem die ersten Dateien auf die Festplatte kopiert wurden,
fragt Debian ob CDs mit Paketen eingelesen werden sollen. Dieser Schritt sollte mit <code
@ -52,13 +52,13 @@
weitergeben. Im nächsten Schritt sollte nur der SSH Server aktiviert werden. Die Optionen können deaktiviert
oder aktiviert werden, indem mit den Pfeiltasten auf die entsprechende Option navigiert wird und dann die
Leertaste gedrückt wird.</p>
<jl-img src="/img/anleitungen/mailcow/debian_scan_media.jpg"></jl-img>
<jl-img src="/img/anleitungen/mailcow/debian_survey.jpg"></jl-img>
<jl-img src="/img/anleitungen/mailcow/debian_software.jpg"></jl-img>
<img src="../assets/img/debian_scan_media.jpg">
<img src="../assets/img/debian_survey.jpg">
<img src="../assets/img/debian_software.jpg">
<p>Nachdem der SSH Server installiert wurde, muss der Bootloader installiert werden, dafür muss bei der Frage ob
Grub installiert werden soll "Yes" gedrückt werdeb und in der nächsten Seite die Systemfestplatte ausgewählt
werden.</p>
<jl-img src="/img/anleitungen/mailcow/debian_grub_install.jpg"></jl-img>
<img src="../assets/img/debian_grub_install.jpg">
<p>Nachdem das System installiert wurde wird Debian neugestartet. Nun kann sich entweder über die Oberfläche
angemeldet werden oder via SSH über die IP Adresse. Nach der Authentifizierung am System mit dem zuvor
erstellten Nutzer muss sich als root angemeldet werden und danach das System auf den neuesten Stand gebracht
@ -127,7 +127,7 @@ cd mailcow-dockerized
die IP Adresse der reverse DNS Eintrag auf den gleichen Domain wie im MX Eintrag geändert werden (also in meinem
Fall auf <code class="language-text">mail.jonasled-test.xyz</code>)
</p>
<jl-img src="/img/anleitungen/mailcow/mailcow_dns.jpg"></jl-img>
<img src="../assets/img/mailcow_dns.jpg">
<p>
Wenn nun alle DNS Einstellungen laufen kann Mailcow das erste mal mit dem nachfolgenden Befehl gestartet
werden. Beim ersten mal werden alle Programme heruntergeladen, abhängig von der Internetgeschwindigkeit kann
@ -139,14 +139,14 @@ cd mailcow-dockerized
# nicht benötigt falls noch als root angemeldet aus dem vorherigen Schritt.
docker-compose up</code>
</pre>
<jl-img src="/img/anleitungen/mailcow/mailcow_login.jpg"></jl-img>
<img src="../assets/img/mailcow_login.jpg">
<p>
Nachdem das oben abgebildete Login Fenster angezeigt wird, ist Mailcow fertig gestartet. Der default
Nutzername ist admin mit dem Passwort moohoo, dieses sollte umgehend nach dem ersten Login abgeändert
werden. Dazu in der Benutzerübersicht beim Admin Benutzer auf <code class="language-text">edit</code>
(blauer Button im Bild unten) klicken
und ein neues Passwort mit
einem <a href="/passwordgen.html">Passwortgenerator</a> erstellen und speichern. Als nächstes
einem <a href="../passwordgen.html">Passwortgenerator</a> erstellen und speichern. Als nächstes
empfehle ich dringend ein Zweifaktor Login festzulegen. Dazu kann entweder wenn ein passender <a
href="https://www.amazon.de/dp/B07HBD71HL/">Hardwareschlüssel</a> vorhanden ist WebAuthn oder Yubico
verwendet werden. Wenn kein Hardwareshlüssel vorhanden ist, können time based OTP Keys verwendet werden.
@ -161,22 +161,22 @@ docker-compose up</code>
class="language-text">Mailboxes</code> können nun
Mailboxen angelegt werden.
</p>
<jl-img src="/img/anleitungen/mailcow/mailcow_setup_mail.jpg"></jl-img>
<jl-img src="/img/anleitungen/mailcow/mailcow_domain_setup.jpg"></jl-img><br>
<jl-img src="/img/anleitungen/mailcow/mailcow_domain_new_1.jpg"></jl-img>
<jl-img src="/img/anleitungen/mailcow/mailcow_domain_new_2.jpg"></jl-img>
<img src="../assets/img/mailcow_setup_mail.jpg">
<img src="../assets/img/mailcow_domain_setup.jpg"><br>
<img src="../assets/img/mailcow_domain_new_1.jpg">
<img src="../assets/img/mailcow_domain_new_2.jpg">
<p>
In der Mailbox Konfiguration kann nun mit dem Button <code class="language-text">Add mailbox</code> eine
neue Mailbox angelegt werden. Hier
muss der Teil der Mail vor dem <code class="language-text">@</code> angegeben werden. (Beispielswiese für
die Mail <code class="language-text">info@jonasled-test.xyz</code>
muss hier info angegeben werden) Danach sollte der volle Name des Nutzers und ein Passwort aus einem
<a href="/passwordgen.html/">Passwortgenerator</a> festgelegt werden. Wenn nun alle
<a href="../passwordgen.html">Passwortgenerator</a> festgelegt werden. Wenn nun alle
Einstellungen passen, kann der Domain mit <code class="language-text">Add</code> angelegt werden. Nun kann
sich der Nutzer ins SOGo anmelden
um das Webmail zu nutzen oder mit einem Client wie Thunderbird anmelden.
</p>
<jl-img src="/img/anleitungen/mailcow/mailcow_mailbox_new.jpg"></jl-img>
<img src="../assets/img/mailcow_mailbox_new.jpg">
<p>
Nachdem wir nun die erste Mailbox erstellt haben, muss noch ein DNS Eintrag erstellt werden, damit andere
Server validieren können, dass der sendende Server wirklich authorisiert dazu ist. Dazu im Mailcow Admin
@ -184,8 +184,6 @@ docker-compose up</code>
Configuration auf ARC/DKIM Keys gehen. Danach den beim Domain angegebenen Key kopieren und in der DNS
Verwaltung als TXT Record mit der Bezeichnung <code class="language-text">dkim._domainkey</code> eintragen.
</p>
<jl-img src="/img/anleitungen/mailcow/mailcow_dkim_webui.jpg"></jl-img>
<jl-img src="/img/anleitungen/mailcow/mailcow_dkim_dns.jpg"></jl-img>
<p>
Nun ist unser Mail Server vollständig konfiguriert und kann auch eingesetzt haben. Um die Funktion zu testen
sollte zuerst mit einem anderen Anbieter eine Mail an eine Adresse auf dem neuen Server gesendet werden.
@ -193,12 +191,11 @@ docker-compose up</code>
href="https://www.mail-tester.com/">mail-tester.com</a>. Auf dieser bekommt man eine Mail Adresse, an
welche man eine Mail senden kann und danach alle fehler angezeigt bekommt.
</p>
<h2>Kommentare:</h2>
<jl-comments_display></jl-comments_display>
<jl-new_comment id="newComment"></jl-new_comment>
<img src="../assets/img/mailcow_dkim_webui.jpg">
<img src="../assets/img/mailcow_dkim_dns.jpg">
</div>
<jl-footer></jl-footer>
<script src="/js/script.js"></script>
<script src="../js/script.js" type="module"></script>
<script>
document.title = "Mailcow installieren - Jonas Leder";
</script>

View file

@ -5,7 +5,7 @@
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title></title>
<link href="/css/style.css" rel="stylesheet">
<link href="../scss/style.scss" rel="stylesheet">
</head>
<body>
@ -70,7 +70,7 @@ chown apache /var/www/localhost/htdocs/ -R</code>
installiert. Nachdem dieser Schritt erfolgreich durchlaufen ist landen wir auf der Setup-Seite, dort müssen wir
unsere Datenbank und unseren admin Nutzer wie unten im Bild zu sehen angeben. Falls gewünscht unten den Haken
bei den empfohlenden Anwendungen entfernen.<br>
<jl-img src="/img/anleitungen/nextcloud-setup.png"></jl-img><br>
<img src="../assets/img/nextcloud-setup.png"><br>
Nachdem die Datenbank angelegt und alle Apps installiert wurden, solltest du auf der Startseite der Nextcloud
gelandet sein. Optional kann nun für mehr performance noch ein Memory-Caching konfiguriert werden. Wie dieses
eingerichtet ist, kann dem <a
@ -84,4 +84,4 @@ chown apache /var/www/localhost/htdocs/ -R</code>
<jl-footer></jl-footer>
<script src="/js/script.js"></script>
<script src="../js/script.js" type="module"></script>

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title></title>
<link href="/css/style.css" rel="stylesheet">
<link href="../scss/style.scss" rel="stylesheet">
</head>
<body>
<jl-header data-title="Installation von Snowboy"></jl-header>
@ -31,7 +31,7 @@ build-essential git python3-setuptools python3-dev autotools-dev automake</code>
<pre><code class="language-bash">wget http://downloads.sourceforge.net/swig/swig-3.0.12.tar.gz
tar -xovzf swig-3.0.12.tar.gz
cd swig-3.0.12
wget https://sourceforge.net/projects/pcre/8.42/pcre-8.42.tar.gz
wget https://sourceforge.net/projects/pcre../assets/files/pcre/8.42/pcre-8.42.tar.gz
./Tools/pcre-build.sh
./autogen
./configure
@ -57,8 +57,8 @@ make</code></pre>
Rate". Das Problem der Soundkarte ist, dass sie nur 44kHz als Abtastrate unterstützt, Snowboy braucht aber eine
Abtastrate von 16kHz wie ich daraufhin nachgelesen habe. Unten sind zwei Screenshots der Fehler angefügt.</p>
<br>
<jl-img src="/img/anleitungen/snowboy/snowboy_no_mic.png"></jl-img>
<jl-img src="/img/anleitungen/snowboy/snowboy_wrong_sample_rate.png"></jl-img>
<img src="../assets/img/snowboy_no_mic.png">
<img src="../assets/img/snowboy_wrong_sample_rate.png">
<h2>Kommentare:</h2>
<jl-comments_display></jl-comments_display>
<jl-new_comment id="newComment"></jl-new_comment>
@ -66,4 +66,4 @@ make</code></pre>
<jl-footer></jl-footer>
<script src="/js/script.js"></script>
<script src="../js/script.js" type="module"></script>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

Before

Width:  |  Height:  |  Size: 827 KiB

After

Width:  |  Height:  |  Size: 827 KiB

View file

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View file

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View file

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 187 KiB

View file

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

View file

Before

Width:  |  Height:  |  Size: 253 KiB

After

Width:  |  Height:  |  Size: 253 KiB

View file

Before

Width:  |  Height:  |  Size: 1 MiB

After

Width:  |  Height:  |  Size: 1 MiB

View file

Before

Width:  |  Height:  |  Size: 1,005 KiB

After

Width:  |  Height:  |  Size: 1,005 KiB

View file

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View file

Before

Width:  |  Height:  |  Size: 963 KiB

After

Width:  |  Height:  |  Size: 963 KiB

View file

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 119 KiB

View file

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

View file

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View file

Before

Width:  |  Height:  |  Size: 735 KiB

After

Width:  |  Height:  |  Size: 735 KiB

View file

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View file

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View file

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View file

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View file

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View file

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View file

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View file

Before

Width:  |  Height:  |  Size: 243 KiB

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 MiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

BIN
src/assets/img/hpServer.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 KiB

View file

Before

Width:  |  Height:  |  Size: 939 KiB

After

Width:  |  Height:  |  Size: 939 KiB

View file

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Some files were not shown because too many files have changed in this diff Show more