This commit is contained in:
2026-05-18 14:28:59 +02:00
parent 4d80391293
commit b2ce6ea00b
15 changed files with 525 additions and 1 deletions

View File

@@ -1,2 +1,3 @@
# blogcontenttemplate
# blogcontent
Inhalte des Blogs

View File

@@ -0,0 +1,87 @@
<!--{"title": "Docker Swarm mit abweichendem Port einrichten", "date": "2025-02-06", "slug": "docker-swarm-mit-abweichendem-port-einrichten", "cover": "/static/img/docker-swarm-mit-abweichendem-port-einrichten.webp"}-->
> Kennzeichnung gemäß Artikel 52 Absatz 1 EU AI Act: [💜 <ins>Kein Einsatz von KI</ins>](/page/ai)
**Wir möchten in diesem Artikel über die abweichende Einrichtung eines Swarm-Netzwerks unter vmware berichten. Eine Besonderheit in der Konfiguration von Docker-Swarm unter vmware kann der identische Port (4789) sein, den sowohl vmware, als auch Docker-Swarm nutzen.**
## Einleitung
Wir möchten in diesem Artikel über die abweichende Einrichtung eines Swarm-Netzwerks unter vmware berichten. Eine Besonderheit in der Konfiguration von Docker-Swarm unter vmware kann der identische Port (4789) sein, den sowohl vmware, als auch Docker-Swarm nutzen. Hier ist es notwendig, den ```data-path-port``` zu ändern. Wir gehen hier Schritt für Schritt die Konfiguration durch.
## Umgebung
Wir installieren drei Swarm-Nodes auf einer vmware-Umgebung mit insgesamt 4 Hosts. Die Anzahl der vmware-Hosts ist jedoch irrelevant. Vorausgesetzt werden drei Ubuntu-Core-Server mit der OS-Version 22.04 ohne weitere Konfiguration.
## Basis-Konfiguration
Wir wechseln in den autorisierten Modus um administrative Aktionen ausführen zu können:
Dazu führen wir das Kommando ```sudo su``` aus.
Danach installieren wir die Net-Tools mit dem Kommando ```apt install net-tools```.
Danach installieren wir den Text-Editor nano mit dem Kommando ```apt install nano```.
Im Anschluss deaktivieren wir den Ubuntu-Cloud-Init. Hierzu suchen wir mit dem Kommando ```apt-cache pkgnames | grep cloud-init``` die passende Konfiguration.
Die angezeigte Datei sollte nun bearbeitet werden: ```nano /etc/cloud/cloud.cfg.d/99-disable-cloud-init.cfg```.
Wir ergänzen die Konfiguration um die Zeile ```network: {config: disabled}```.
Jetzt ist ein Neustart der virtuellen Maschinen notwendig.
Im Anschluss konfigurieren wir die Netzwerkeinstellungen der virtuellen Maschinen. Führen Sie die zwei Kommandos aus:
```
cd /etc/netplan
grep -iH enp1s0 *.*
```
Hierbei sollte eine Datei angezeigt werden, die ungefähr so aussieht: ```00-installer-config.yaml```.
Jetzt bearbeiten wir die Datei mit derm Kommando ```nano 00-installer-config.yaml``` und fügen folgenden Inhalt ein:
```
network:
ethernets:
enp1s0:
dhcp4: no
addresses:
- 192.168.122.128/24
gateway4: 192.168.122.1
nameservers:
addresses: [1.1.1.1, 1.0.0.1]
version: 2
```
Jetzt aktivieren wir die Konfiguration mit dem Kommando ```netplan apply```.
## Installation von Docker
Installieren Sie Docker mit dem Kommando ```apt install docker.io```.
## Einrichten des Swarm (nur auf dem ersten Server)
Führen Sie foldendes Kommando aus um den Swarm-Modus zu aktivieren. Besonders hervorzuheben, ist der abweichende Port. Dieser hat uns bei unser Installation in dieser Kombination Kopfzerbrechen bereitet. Deswegen teilen wir unseren Lösungsweg:
```
docker swarm init --data-path-port 4788 --advertise-addr 172.29.8.15
```
Beachten Sie, hier bei ```--advertise-addr``` die IP-Adresse Ihrer ersten virtuellen Maschine einzugeben!
Nachdem Sie den Befehl ausgeführt haben wird Swarm initialisiert. Achten Sie hierbei auf die Ausgabe. Es sollte eine Meldung mit Ihrem "Swarm-Key" ersichtlich sein. Sollte der Key nicht angezeigt werden, können Sie ihn mit dem Kommando ```docker swarm join-token worker``` erneut aufrufen.
Hinzufügen der zwei anderen Server zum Swarm
Mit nur einer Node ist ein Swarm ziemlich unnötig. Deswegen fügen wir die zwei weiteren virtuellen Maschinen hinzu:
```docker swarm join --token --KEY-- 172.29.8.15:2377```.
Diesen Befehl führen sie auf jedem Server aus, der dem Swarm beitreten soll. Ersetzen Sie --KEY-- durch Ihren Swarm-Key.
Mit den folgenden Befehlen können Sie Informationen zu Ihrem Swarm abrufen:
```docker info```
```docker node ls```
Jetzt sind alle Server Teil des Swarm.
Möchten Sie die anderen Server ebenfalls zu Manager-Nodes machen, führen Sie diesen Befehl auf dem ersten Server aus: ```docker node promote name```.

47
pages/ai.md Normal file
View File

@@ -0,0 +1,47 @@
<!--{"title": "Kennzeichnungspflicht gemäß EU AI Act", "slug": "ai"}-->
Ich legen offen, in welchem Umfang künstliche Intelligenz (KI) bei der Erstellung meiner Inhalte beteiligt war.\
Alle Beiträge sind mit einem eindeutigen Hinweis versehen, damit Leserinnen und Leser nachvollziehen können, ob und wie KI eingesetzt wurde.
**Die Kennzeichnung erfolgt nach folgendem Schema:**
> ### 💜 Kein Einsatz von KI
> - Für die inhaltliche Erstellung des Beitrags kam keine KI zum Einsatz.
> - Der Artikel wurde eigenhändig erstellt und überprüft.
> - Das Titelbild **kann** von einer KI erstellt sein.
Diese Beiträge werden so gekennzeichnet:
> Kennzeichnung gemäß Artikel 52 Absatz 1 EU AI Act: [💜 <ins>Kein Einsatz von KI</ins>](/page/ai)
---
> #### 🌱 Kennzeichnung gemäß Artikel 52 Absatz 1 EU AI Act: <ins>Rechercheunterstützung</ins>
> - Die KI sammelt relevante Quellen, Daten und Fakten, um dem Autor eine fundierte Grundlage für den Beitrag zu liefern.
> - Der Artikel wurde redaktionell überarbeitet und überprüft.
> - Das Titelbild **kann** von einer KI erstellt sein.
Diese Beiträge werden so gekennzeichnet:
> Kennzeichnung gemäß Artikel 52 Absatz 1 EU AI Act: [🌱 <ins>Rechercheunterstützung</ins>](/page/ai)
---
> #### 💬 Kennzeichnung gemäß Artikel 52 Absatz 1 EU AI Act: <ins>Inhaltliche Zusammenfassung</ins>
> - Die KI fasst längere Texte oder Studien prägnant zusammen, damit der Autor Kernaussagen effizient weiterverarbeiten kann.
> - Der Artikel wurde redaktionell überarbeitet und überprüft.
> - Das Titelbild **kann** von einer KI erstellt sein.
Diese Beiträge werden so gekennzeichnet:
> Kennzeichnung gemäß Artikel 52 Absatz 1 EU AI Act: [💬 <ins>Inhaltliche Zusammenfassung</ins>](/page/ai)
---
> #### ⚡ Kennzeichnung gemäß Artikel 52 Absatz 1 EU AI Act: <ins>Vollständige Inhaltserstellung</ins>
> - Die KI generiert eigenständig komplette Blogartikel, einschließlich Struktur, Formulierungen und abschließendem Feinschliff.
> - Der Artikel wurde redaktionell überarbeitet und überprüft.
> - Das Titelbild **kann** von einer KI erstellt sein.
Diese Beiträge werden so gekennzeichnet:
> Kennzeichnung gemäß Artikel 52 Absatz 1 EU AI Act: [⚡ <ins>Vollständige Inhaltserstellung</ins>](/page/ai)

34
pages/impressum.md Normal file
View File

@@ -0,0 +1,34 @@
<!--{"title": "Impressum", "slug": "impressum"}-->
### Name und Anschrift
Dein Name\
Adresse 1\
12345 Test-Stadt
### Kontakt
E-Mail: mail@example.com\
Telefon: +49 (1234) 12345-6
### Inhaltlich verantwortlich
Dein Name\
Adresse 1\
12345 Test-Stadt
### Weitere Informationen
Die auf dieser Webseite veröffentlichten kostenlosen und frei zugänglichen Inhalte wurden mit größter Sorgfalt erstellt.
Diese Website enthält Links zu externen Websites Dritter. Für diese Websites sind die jeweiligen Betreiber verantwortlich.\
Der Anbieter hat die externen Links bei ihrer erstmaligen Erstellung auf mögliche Rechtsverstöße überprüft und keine festgestellt.\
Auf die aktuelle und zukünftige Gestaltung sowie auf die Inhalte der verlinkten Seiten hat der Anbieter keinen Einfluss.\
Das Setzen externer Links bedeutet nicht, dass sich der Anbieter die Inhalte dieser Seiten zu eigen macht.\
Eine kontinuierliche Überprüfung der externen Links ist für den Anbieter ohne konkrete Hinweise auf Rechtsverstöße nicht zumutbar.\
Bei Bekanntwerden von Rechtsverstößen werden derartige externe Links jedoch umgehend entfernt.
Die auf dieser Website veröffentlichten Inhalte sind durch das deutsche Urheber- und Leistungsschutzrecht geschützt.\
Jede Verwertung, die nicht durch das deutsche Urheber- und Leistungsschutzrecht gestattet ist,\
erfordert die vorherige schriftliche Zustimmung des Anbieters oder des jeweiligen Rechteinhabers.\
Dies gilt insbesondere für die Vervielfältigung, Bearbeitung, Übersetzung, Speicherung,\
Verarbeitung oder Wiedergabe von Inhalten in Datenbanken oder anderen elektronischen Medien und Systemen.\
Inhalte und Rechte Dritter sind entsprechend gekennzeichnet.\
Die unerlaubte Vervielfältigung oder Verbreitung einzelner Inhalte oder kompletter Seiten ist nicht gestattet und strafbar.\
Nur die Erstellung von Kopien und Downloads für den persönlichen, privaten und nicht kommerziellen Gebrauch ist erlaubt.

66
pages/privacy.md Normal file
View File

@@ -0,0 +1,66 @@
<!--{"title": "Datenschutzerklärung", "slug": "datenschutzerklaerung"}-->
## Angaben gemäß § 5 des Telemediengesetzes (TMG)
Verantwortlicher für diese Website (im Folgenden „Blog“):
### Name und Anschrift
Dein Name\
Adresse 1\
12345 Test-Stadt
### Kontakt
E-Mail: mail@example.com\
Telefon: +49 (1234) 12345-6
---
## Allgemeine Hinweise und Pflichtinformationen
Mir ist der Schutz deiner Daten besonders wichtig. Deshalb gelten auf diesem Blog folgende Grundsätze:
1. Keine Cookies
Auf diesem Blog werden keinerlei Cookies eingesetzt. Es werden weder technische noch optionale Cookies gesetzt oder gespeichert.\
Mein Blog ist so programmiert, das er ohne die Speicherung von Informationen auskommt. Weder auf meinem Server von auf deinem Endgerät!
2. Keine Affiliate-Links
Es gibt keine Affiliate-Links oder andere kommerzielle Verlinkungen, die dazu dienen, Besucheraktivitäten zu erfassen oder Provisionen zu generieren.\
Dieser Blog ist ein Hobby und dient nicht zur Generierung von Einkommen.
3. Kein Tracking
Natürlich wäre es spannend zu wissen, über welche Wege die Nutzer hierher gefunden haben.\
Solche Informationen könnten helfen, Inhalte gezielter auszurichten oder den Blog weiterzuentwickeln. Trotzdem steht für mich eines an erster Stelle: dein Vertrauen.\
Die Grundsätze dieses Blogs keine Überwachung, kein Tracking, keine Analyse des Nutzerverhaltens haben Vorrang vor jeder Statistik.\
Deshalb verzichte ich bewusst auf den Einsatz von Tools wie Google Analytics, Matomo oder ähnlichen Diensten.\
Die einzige Funktion ist das anonyme "hochzählen" von Aufrufen eines Artikels. Das hilft mir zu verstehen, wie hoch das Interesse an einem bestimmten Thema ist.\
Dies ist eine Funktion meinen selbst erstellten Webservers und steht in keinem Zusammenhang zum aufrufenden Besucher.\
Wenn ein Besucher also 50x F5 drückt, sind dies ungeachtet 50 Aufrufe.
4. Keine Drittanbieter-Dienste
Auf diesem Blog werden keine Inhalte von Drittanbietern eingebunden.\
Es werden weder externe Schriftarten (z.B. Google Fonts), noch Skripte, noch Social-Media-Plugins oder Werbenetzwerke verwendet.\
Sämtliche Inhalte werden lokal und unabhängig bereitgestellt.
5. Keine Erhebung personenbezogener Daten
Ich erhebe, speichere oder verarbeite keine personenbezogenen Daten.\
Es gibt keine Formulare, keine Kommentarfunktion, keinen Newsletter und keine anderen Möglichkeiten, personenbezogene Daten zu übermitteln.\
Bitte sende mir auch bei Kontaktanfragen keine personenbezogenen Daten, wenn dies nicht erforderlich ist.
6. Statischer Inhalt, keine Interaktion
Der Blog besteht ausschließlich aus statischen Inhalten.\
Es gibt keine Möglichkeit zur Interaktion, kein Login, keine Registrierung und keine nutzergenerierten Inhalte.
7. Kontakt
Wenn du Fragen hast oder weitere Informationen benötigst, kannst du mich gerne kontaktieren. Die Kontaktmöglichkeiten findest du im Impressum.\
Auch hier bitte ich dich, auf die Übermittlung personenbezogener Daten zu verzichten, soweit dies möglich ist.
**Hinweis**
Da auf diesem Blog keine personenbezogenen Daten verarbeitet werden, entfällt die Pflicht zur Benennung eines Datenschutzbeauftragten gemäß Art. 37 DSGVO.
---
Sollte sich der Umfang dieses Blogs ändern und personenbezogene Daten betroffen sein, wird diese Datenschutzerklärung entsprechend aktualisiert.
---
Diese Maßnahmen dienen dem Ziel, ein Höchstmaß an Privatsphäre und Datenschutz für alle Besucher sicherzustellen.\
Wenn du Fragen hast oder weitere Informationen benötigst, kannst du mich gerne kontaktieren die Kontaktmöglichkeiten findest du im Impressum,\
aber auch hier gilt: bitte ohne Übermittlung personenbezogener Daten.

17
pages/welcome.md Normal file
View File

@@ -0,0 +1,17 @@
<!--{"title": "Willkommen bei B1tsBlog!", "slug": "welcome"}-->
**Herzlich willkommen auf meinem privaten Blog! Schön, dass du hierher gefunden hast.**
Ich bin ein IT-Professional mit Leidenschaft für Technologie, Softwareentwicklung und digitale Trends. In diesem Blog teile ich Gedanken, Ideen und Erfahrungen aus meinem Berufsleben aber auch Themen, die mich in meiner Freizeit beschäftigen. Es ist mir wichtig, hier nicht nur Fachinhalte zu präsentieren, sondern auch nützliche, inspirierende und manchmal einfach unterhaltsame Beiträge zu liefern!
### Worum geht es hier?
Der Blog deckt eine breite Palette an Themen ab: von Programmierung, IT-Sicherheit und digitalen Tools bis hin zu persönlichen Projekten, Hobbys und allgemeinen Beobachtungen aus der digitalen Welt. Mein Ziel ist es, Inhalte zu veröffentlichen, die anderen weiterhelfen, Denkanstöße geben oder einfach interessant zu lesen sind.
### Einsatz von KI-generierten Inhalten
Ein Teil der Inhalte, insbesondere Bilder, wird mit Hilfe von Künstlicher Intelligenz erzeugt. Ich nutze diese Technologie, um kreative und visuell ansprechende Elemente zu erstellen, die den Blog bereichern. Dabei lege ich Wert darauf, transparent zu machen, wo KI zum Einsatz kommt.
Weitere Informationen können hier gefunden werden: [Kennzeichnungspflicht gemäß EU AI Act](/page/ai)
---
Ich freue mich, wenn du hier etwas findest, das dich inspiriert, dir weiterhilft oder einfach Spaß macht. Viel Freude beim Lesen und Entdecken!

Binary file not shown.

Binary file not shown.

BIN
static/img/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
static/img/placeholder.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

182
static/main.css Normal file
View File

@@ -0,0 +1,182 @@
/* ---------- Local WebFonts ---------- */
@font-face {
font-family: "Fira Code";
src: url("/static/fonts/FiraCode-VF.woff2") format("woff2");
font-weight: 400 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inter";
src: url("/static/fonts/InterVariable.woff2") format("woff2");
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
/* ---------- Farbpalette ---------- */
:root {
/* Light theme */
--bg: #f5f7fa;
--bg-alt: #ffffff;
--card-bg: #ffffff;
--text: #000000;
--text-muted: #1f2933;
--accent: #2563eb; /* Indigo600 */
--accent-light: #3b82f6; /* Indigo500 */
--code-bg: #f1f5f9;
--code-border: #e2e8f0;
--radius: 0.75rem;
--gap: 2rem;
--shadow: 0 4px 16px rgba(0,0,0,.08);
font-size: 16px;
font-family: "Inter", system-ui, sans-serif;
color-scheme: light;
}
/* Dark mode (optional) */
@media (prefers-color-scheme: dark) {
:root {
--bg: #0d1117;
--bg-alt: #161b22;
--card-bg: #161b22;
--text: #ffffff;
--text-muted: #e4e8ec;
--accent: #3b82f6;
--accent-light:#60a5fa;
--code-bg: #1e242c;
--code-border: #30363d;
--shadow: 0 4px 16px rgba(0,0,0,.32);
}
}
/* ---------- Grundlayout ---------- */
* { box-sizing: border-box; }
body {
margin: 0;
background: var(--bg);
color: var(--text);
line-height: 1.65;
}
a {
color: var(--accent);
text-decoration: underline dotted;
transition: color .15s;
}
a:hover { color: var(--accent-light); }
a.no-underline {
text-decoration: none;
}
/* Container in der Mitte */
.wrapper {
max-width: 1200px;
margin: 0 auto;
padding: var(--gap) calc(var(--gap) / 1.5);
}
img.footer {
max-width: 150px;
max-height: 60px; /* oder ein anderer fixer Wert, z. B. 500px */
width: auto;
height: auto;
}
/* ---------- Kopf & Fuß ---------- */
header, footer {
background: var(--bg-alt);
box-shadow: var(--shadow);
}
header {
position: sticky; top: 0; z-index: 10;
display: flex; justify-content: space-between; align-items: center;
padding: 10px calc(var(--gap) / 1.2);
}
header h1 { margin: 0; font-size: 1.4rem; }
footer {
text-align: center;
padding: 2rem 1rem;
color: var(--text-muted);
font-size: .9rem;
margin-top: var(--gap);
}
/* ---------- KartenGrid ---------- */
.post-list {
display: grid;
gap: var(--gap);
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
list-style: none;
padding: 0;
margin: 0;
}
.card {
display: flex;
flex-direction: column;
background: var(--card-bg);
border-radius: var(--radius);
box-shadow: var(--shadow);
overflow: hidden;
transition: transform .2s ease, box-shadow .2s ease;
}
.card:hover { transform: translateY(-6px); box-shadow: 0 6px 24px rgba(0,0,0,.1); }
.card img {
width: 100%; height: 180px; object-fit: cover;
}
.card-content { padding: 1rem 1.25rem 1.5rem; }
.card h2 { margin: .25rem 0 .5rem; font-size: 1.25rem; line-height: 1.3; }
.card time { color: var(--text-muted); font-size: .85rem; }
/* ---------- Artikel ---------- */
.hero {
width: 100%; height: 320px; object-fit: cover;
border-radius: var(--radius);
box-shadow: var(--shadow);
}
article h1 { font-size: 2.2rem; margin: 1.2rem 0 .3rem; }
article time { color: var(--text-muted); font-size: .9rem; }
article img:not(.hero), article video {
max-width: 100%; height: auto; border-radius: var(--radius);
box-shadow: var(--shadow);
margin: 1rem 0;
}
article pre {
background: var(--code-bg);
border: 1px solid var(--code-border);
padding: 1rem 1.2rem;
border-radius: var(--radius);
overflow: auto;
font-family: "Fira Code", Consolas, monospace;
}
article blockquote {
border-left: 4px solid var(--accent);
padding: .5rem 1rem; margin: 1rem 0;
background: var(--code-bg);
color: var(--text-muted);
}
code {
background: var(--code-bg);
color: var(--text-muted);
}
.main-nav ul {
display: flex;
gap: 1.25rem;
list-style: none;
margin: 0;
padding: 0;
}
.main-nav a {
font-weight: 600;
color: var(--text);
}
.main-nav a:hover {
color: var(--accent);
}
/* ---------- Responsive ---------- */
@media (max-width: 640px) {
:root { font-size: 15px; }
.hero { height: 200px; }
header { flex-direction: column; gap: .5rem; }
}

20
templates/article.html Normal file
View File

@@ -0,0 +1,20 @@
{{ define "title" }}{{ .Title }}  B1tsblog{{ end }}
{{ define "body" }}
<article>
{{ if .Cover }}
<img class="hero" src="{{ .Cover }}" alt="">
{{ end }}
<p><a class="no-underline" href="/">Zurück zur Übersicht</a></p>
<h1>{{ .Title }}</h1>
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "02.01.2006" }}</time>
<div class="article-content">
{{ .Body }}
</div>
<p><a class="no-underline" href="/">Zurück zur Übersicht</a></p>
</article>
{{ end }}
{{ define "article" }}{{ template "layout" . }}{{ end }}

39
templates/base.html Normal file
View File

@@ -0,0 +1,39 @@
{{ define "layout" }}
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
{{ if .Description }}
<meta name="description" content="{{ .Description }}">
{{ end }}
<title>{{ block "title" . }}B1tsblog{{ end }}</title>
<link rel="icon" type="image/vnd.icon" href="/static/img/favicon.ico">
<link rel="stylesheet" href="/static/main.css">
</head>
<body>
<header>
<h1><a href="/" class="no-underline">B1tsblog</a></h1>
<nav class="main-nav">
<ul>
<li><a class="no-underline" href="/">Start</a></li>
<li><a class="no-underline" href="/page/welcome">Hallo</a></li>
<li><a class="no-underline" href="/page/ai">KI</a></li>
<li><a class="no-underline" href="/page/datenschutzerklaerung">Datenschutz</a></li>
<li><a class="no-underline" href="/page/impressum">Impressum</a></li>
</ul>
</nav>
</header>
<main class="wrapper">
{{ template "body" . }}
</main>
<footer class="wrapper">
© {{ now.Year }} · Jan Bergner / B1tsBlog
<hr>
Ich verzichte auf Cookies, affiliate Links, Tracking und die Einbindung von Drittanbieter-Diensten.
</footer>
</body>
</html>
{{ end }}

19
templates/list.html Normal file
View File

@@ -0,0 +1,19 @@
{{ define "body" }}
<ul class="post-list">
{{ range .Articles }}
<li>
<a class="card no-underline" href="/post/{{ .Slug }}">
{{ if .Cover }}
<img src="{{ .Cover }}" alt="">
{{ else }}
<img src="/static/img/placeholder.webp" alt="">
{{ end }}
<div class="card-content">
<h2>{{ .Title }}</h2>
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "02.01.2006" }}</time> ({{ .Counter }})
</div>
</a>
</li>
{{ end }}
</ul>
{{ end }}

12
templates/page.html Normal file
View File

@@ -0,0 +1,12 @@
{{ define "title" }}{{ .Title }}  B1tsblog{{ end }}
{{ define "body" }}
<article>
<p><a class="no-underline" href="/">Zurück</a></p>
<h1>{{ .Title }}</h1>
<div class="article-content">{{ .Body }}</div>
<p><a class="no-underline" href="/">Zurück</a></p>
</article>
{{ end }}
{{ define "page" }}{{ template "layout" . }}{{ end }}