Compare commits
81 Commits
a1471fc310
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c38fd7f435 | |||
| 7ef6b551ac | |||
| dce90ca88b | |||
| 7493ca278e | |||
| 445db0ec10 | |||
| 87493fe40c | |||
| ca22dd8596 | |||
| e4d47b46e4 | |||
| 4c41bb8838 | |||
| 98de9b1b98 | |||
| 621db424f0 | |||
| 7e21b10a6c | |||
| 8cd66c9f59 | |||
| 96894516a7 | |||
| 2e31bfdc77 | |||
| 08ef9c7132 | |||
| 847d860144 | |||
| 8b7f1127f8 | |||
| 278d3aa48f | |||
| 9dca6a57df | |||
| 16b5ddfb7b | |||
| 12ce629b31 | |||
| 5f089d727e | |||
| d7726b57e4 | |||
| c25c60ab27 | |||
| 7ee0c9e9f7 | |||
| 0d98bd064e | |||
| aba08de902 | |||
| 4764d1536c | |||
| 3357061c79 | |||
| 2a63985564 | |||
| a83e360317 | |||
| 33c0f9d954 | |||
| 7b598a04be | |||
| 8554e13ebf | |||
| f6806c5ad0 | |||
| c0f985982e | |||
| 08cb7b689d | |||
| 17424d399d | |||
| 2e5f9b6fa5 | |||
| cd869b610e | |||
| 7873cbe132 | |||
| 32d3fc33d6 | |||
| 8d77eb94e8 | |||
| 7afa89a66b | |||
| 0fb3924382 | |||
| b2dc65e024 | |||
| 1b40a248d2 | |||
| d912799a9e | |||
| 477539928c | |||
| d4840c936a | |||
| f534182c20 | |||
| 16d2548976 | |||
| 6017bd2cd3 | |||
| e266c9c78f | |||
| 229f78a7ee | |||
| c88cf623f0 | |||
| b1536ad724 | |||
| 38fa612a18 | |||
| 10172d1fdc | |||
| eeb2709330 | |||
| 0773b2a8e1 | |||
| fb26c8af92 | |||
| c54b4e652a | |||
| 73c068c84f | |||
| 962d3fdd02 | |||
| a179c7e51a | |||
| 8ca50097fd | |||
| f746cea41a | |||
| 7e85585d6c | |||
| 89f7d21483 | |||
| b5a0c1d64e | |||
| e4b060b753 | |||
| 2cdc782395 | |||
| b83eff82a0 | |||
| 1d09537ff0 | |||
| efac316b4c | |||
| 128aa11273 | |||
| dfe7d03445 | |||
| b0eea7ae62 | |||
| 8f7a0b359c |
67
.gitea/workflows/release.yml
Normal file
67
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
name: release-tag
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
jobs:
|
||||||
|
release-image:
|
||||||
|
runs-on: ubuntu-fast
|
||||||
|
env:
|
||||||
|
DOCKER_ORG: ${{ vars.DOCKER_ORG }}
|
||||||
|
DOCKER_LATEST: latest
|
||||||
|
RUNNER_TOOL_CACHE: /toolcache
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
|
- name: Set up Docker BuildX
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
with: # replace it with your local IP
|
||||||
|
config-inline: |
|
||||||
|
[registry."${{ vars.DOCKER_REGISTRY }}"]
|
||||||
|
http = true
|
||||||
|
insecure = true
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ vars.DOCKER_REGISTRY }} # replace it with your local IP
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Get Meta
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
|
||||||
|
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
build-args: |
|
||||||
|
CONTENT_REPO=https://git.send.nrw/b1tsblog/blogcontent.git
|
||||||
|
CONTENT_REF=main
|
||||||
|
platforms: |
|
||||||
|
linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: | # replace it with your local IP and tags
|
||||||
|
${{ vars.DOCKER_REGISTRY }}/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}
|
||||||
|
${{ vars.DOCKER_REGISTRY }}/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}
|
||||||
|
- name: Build and push StarCitizen
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
build-args: |
|
||||||
|
CONTENT_REPO=https://git.send.nrw/b1tsblog/sccontent.git
|
||||||
|
CONTENT_REF=main
|
||||||
|
platforms: |
|
||||||
|
linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: | # replace it with your local IP and tags
|
||||||
|
${{ vars.DOCKER_REGISTRY }}/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:sc-${{ env.DOCKER_LATEST }}
|
||||||
64
.gitea/workflows/staging.yml
Normal file
64
.gitea/workflows/staging.yml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
name: release-tag
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'staging'
|
||||||
|
jobs:
|
||||||
|
release-image:
|
||||||
|
runs-on: ubuntu-fast
|
||||||
|
env:
|
||||||
|
DOCKER_ORG: ${{ vars.DOCKER_ORG }}
|
||||||
|
DOCKER_LATEST: staging
|
||||||
|
RUNNER_TOOL_CACHE: /toolcache
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
|
- name: Set up Docker BuildX
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
with: # replace it with your local IP
|
||||||
|
config-inline: |
|
||||||
|
[registry."${{ vars.DOCKER_REGISTRY }}"]
|
||||||
|
http = true
|
||||||
|
insecure = true
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ vars.DOCKER_REGISTRY }} # replace it with your local IP
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Get Meta
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
|
||||||
|
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
platforms: |
|
||||||
|
linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: | # replace it with your local IP and tags
|
||||||
|
${{ vars.DOCKER_REGISTRY }}/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}
|
||||||
|
${{ vars.DOCKER_REGISTRY }}/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}
|
||||||
|
- name: Build and push StarCitizen
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
build-args: |
|
||||||
|
CONTENT_REPO=https://git.send.nrw/b1tsblog/sccontent.git
|
||||||
|
CONTENT_REF=main
|
||||||
|
platforms: |
|
||||||
|
linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: | # replace it with your local IP and tags
|
||||||
|
${{ vars.DOCKER_REGISTRY }}/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:sc-${{ env.DOCKER_LATEST }}
|
||||||
29
Dockerfile
29
Dockerfile
@@ -1,7 +1,7 @@
|
|||||||
############################
|
############################
|
||||||
# 1) Go‑Build
|
# 1) Go‑Build
|
||||||
############################
|
############################
|
||||||
FROM golang:1.22 AS build
|
FROM golang:1.23.1 AS build
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
@@ -16,19 +16,22 @@ RUN CGO_ENABLED=0 go build -o /blog ./cmd/blog
|
|||||||
FROM alpine/git AS content
|
FROM alpine/git AS content
|
||||||
|
|
||||||
# Parameterisierbar beim docker build --build-arg …
|
# Parameterisierbar beim docker build --build-arg …
|
||||||
ARG CONTENT_REPO=https://github.com/username/blog-content.git
|
ARG CONTENT_REPO=https://git.send.nrw/b1tsblog/blogcontent.git
|
||||||
ARG CONTENT_REF=main
|
ARG CONTENT_REF=main
|
||||||
|
|
||||||
RUN git clone --depth 1 --branch ${CONTENT_REF} ${CONTENT_REPO} /src
|
RUN git clone --depth 1 --branch ${CONTENT_REF} ${CONTENT_REPO} /src
|
||||||
|
|
||||||
# ─── Repack: bring alles in eine saubere Struktur ────────────────
|
# ─── Repack: bring alles in eine saubere Struktur ────────────────
|
||||||
# • Markdown‑Posts: /src/articles/*.md → /out/content
|
# • Markdown‑Posts: /articles/*.md → /out/content
|
||||||
# • Bilder + CSS + JS: /src/web/static/**/* → /out/static
|
# • Bilder + CSS + JS: /static/**/* → /out/static
|
||||||
|
# • statische Seiten: /pages/* → /out/pages
|
||||||
# (Pfad‑Anpassung hier nach DEINEM Repository‑Layout)
|
# (Pfad‑Anpassung hier nach DEINEM Repository‑Layout)
|
||||||
|
|
||||||
RUN mkdir -p /out/content /out/static
|
RUN mkdir -p /out/content /out/static /out/pages /out/templates/
|
||||||
RUN find /src/articles -name '*.md' -exec cp {} /out/content/ \;
|
RUN find /src/articles -name '*.md' -exec cp {} /out/content/ \;
|
||||||
RUN cp -r /src/web/static/* /out/static/
|
RUN cp -r /src/static/* /out/static/
|
||||||
|
RUN cp -r /src/pages/* /out/pages/
|
||||||
|
RUN cp -r /src/templates/* /out/templates/
|
||||||
|
|
||||||
|
|
||||||
############################
|
############################
|
||||||
@@ -38,18 +41,30 @@ FROM debian:bookworm-slim
|
|||||||
|
|
||||||
# (optional) MySQL‑Client für später
|
# (optional) MySQL‑Client für später
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
ca-certificates \
|
ca-certificates git \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# ─── Binärdatei ─────
|
# ─── Binärdatei ─────
|
||||||
COPY --from=build /blog /usr/local/bin/blog
|
COPY --from=build /blog /usr/local/bin/blog
|
||||||
|
|
||||||
# ─── Content + Assets ───
|
# ─── Content + Assets ───
|
||||||
|
RUN mkdir -p /content /static /pages /app /templates /ticks
|
||||||
|
COPY . /app
|
||||||
COPY --from=content /out/content /content
|
COPY --from=content /out/content /content
|
||||||
COPY --from=content /out/static /static
|
COPY --from=content /out/static /static
|
||||||
|
COPY --from=content /out/pages /pages
|
||||||
|
COPY --from=content /out/templates /templates
|
||||||
|
|
||||||
ENV BLOG_CONTENT_DIR=/content
|
ENV BLOG_CONTENT_DIR=/content
|
||||||
ENV BLOG_STATIC_DIR=/static
|
ENV BLOG_STATIC_DIR=/static
|
||||||
|
ENV BLOG_PAGES_DIR=/pages
|
||||||
|
ENV BLOG_TEMPLATES_DIR=/templates
|
||||||
|
ENV BLOG_TICKS_DIR=/ticks
|
||||||
|
ENV GIT_ENABLE=false
|
||||||
|
ENV GIT_REPO=null
|
||||||
|
ENV GIT_BRANCH=main
|
||||||
|
ENV GIT_DIR=/git-temp
|
||||||
|
ENV GIT_INTERVAL=10
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
CMD ["blog"]
|
CMD ["blog"]
|
||||||
|
|||||||
41
Dockerfile_Slim
Normal file
41
Dockerfile_Slim
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
############################
|
||||||
|
# 1) Go‑Build
|
||||||
|
############################
|
||||||
|
FROM golang:1.23.1 AS build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
COPY . .
|
||||||
|
RUN CGO_ENABLED=0 go build -o /blog ./cmd/blog
|
||||||
|
|
||||||
|
############################
|
||||||
|
# 3) Runtime‑Image
|
||||||
|
############################
|
||||||
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
|
# (optional) MySQL‑Client für später
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates git \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# ─── Binärdatei ─────
|
||||||
|
COPY --from=build /blog /usr/local/bin/blog
|
||||||
|
|
||||||
|
# ─── Content + Assets ───
|
||||||
|
RUN mkdir -p /content /static /pages /app /templates /ticks /git-temp
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
|
ENV BLOG_CONTENT_DIR=/content
|
||||||
|
ENV BLOG_STATIC_DIR=/static
|
||||||
|
ENV BLOG_PAGES_DIR=/pages
|
||||||
|
ENV BLOG_TEMPLATES_DIR=/templates
|
||||||
|
ENV BLOG_TICKS_DIR=/ticks
|
||||||
|
ENV GIT_ENABLE=false
|
||||||
|
ENV GIT_REPO=null
|
||||||
|
ENV GIT_BRANCH=main
|
||||||
|
ENV GIT_DIR=/git-temp
|
||||||
|
ENV GIT_INTERVAL=10
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD ["blog"]
|
||||||
23
README.md
23
README.md
@@ -1,2 +1,25 @@
|
|||||||
# b1tsblog
|
# b1tsblog
|
||||||
|
|
||||||
|
## B1tsBlog
|
||||||
|
|
||||||
|
- Anpassung Datenschutzerklärung
|
||||||
|
- Windows Server 2025 Domänen-Controller legt Netzwerkprofil auf Öffentlich fest
|
||||||
|
- TLS-Zertifikat mit SHA3 auf Windows Server 2016 einspielen
|
||||||
|
- Warum personenbezogene Daten nie in die Betreffzeile einer E-Mail gehören
|
||||||
|
- Warum meldet der Client, dass die Default Domain Policy nicht gelesen werden kann
|
||||||
|
- Open Source als Innovationsmotor: Warum Unternehmen auf offene Software setzen
|
||||||
|
- Neue Blogserie: Open Source im Unternehmen
|
||||||
|
- Optimiert für schnelleres Laden mit webp-Inhalten
|
||||||
|
- docker-swarm-mit-abweichendem-port-einrichten
|
||||||
|
- Content-Update 2025-05-05
|
||||||
|
- PHPMyAdmin mit Serverauswahl im Homelab mittels Docker bereitstellen
|
||||||
|
- Windows Fehler 0x80072F8F - Installation optionaler Features schlägt fehl
|
||||||
|
- In Ubuntu den Port 53 - DNS - selber nutzen
|
||||||
|
- Ein eigenes Docker-Image erstellen - so geht's
|
||||||
|
- Der eigene DNS-Server im Homelab
|
||||||
|
- Content-Update 2025-05-04
|
||||||
|
|
||||||
|
## B1ts Star Citizen Blog
|
||||||
|
|
||||||
|
- Anpassung Datenschutzerklärung
|
||||||
|
- Invictus 2955 - Das Jahr der Idris-P?
|
||||||
316
cmd/blog/main.go
316
cmd/blog/main.go
@@ -1,17 +1,74 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.send.nrw/sendnrw/b1tsblog/internal/article"
|
"git.send.nrw/sendnrw/b1tsblog/internal/article"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var TickCatalog []TickEntry
|
||||||
|
|
||||||
|
func SaveTickCatalog(filename string) error {
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(file)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
return encoder.Encode(TickCatalog)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadTickCatalog(filename string) error {
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
return json.NewDecoder(file).Decode(&TickCatalog)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IncTick(xSlug string) error {
|
||||||
|
for a, b := range TickCatalog {
|
||||||
|
if b.Slug == xSlug {
|
||||||
|
TickCatalog[a].Count = TickCatalog[a].Count + 1
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newEntry := TickEntry{Slug: xSlug, Count: 1}
|
||||||
|
TickCatalog = append(TickCatalog, newEntry)
|
||||||
|
return fmt.Errorf("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTick(xSlug string) string {
|
||||||
|
for _, b := range TickCatalog {
|
||||||
|
if b.Slug == xSlug {
|
||||||
|
var n int64 = b.Count
|
||||||
|
return strconv.FormatInt(n, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
type TickEntry struct {
|
||||||
|
Slug string `json:"slug"`
|
||||||
|
Count int64 `json:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
func getenv(k, d string) string {
|
func getenv(k, d string) string {
|
||||||
if v := os.Getenv(k); v != "" {
|
if v := os.Getenv(k); v != "" {
|
||||||
return v
|
return v
|
||||||
@@ -39,39 +96,69 @@ var (
|
|||||||
tplArticle *template.Template
|
tplArticle *template.Template
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var Xarticles []article.Article
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
|
// Signal-Kanal einrichten
|
||||||
|
stop := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
// Goroutine, die auf Signale wartet
|
||||||
|
go func() {
|
||||||
|
<-stop
|
||||||
|
fmt.Println("Stop Sign...")
|
||||||
|
prepareExit()
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// --- Verzeichnisse konfigurierbar machen -------------------------
|
||||||
|
contentDir := getenv("BLOG_CONTENT_DIR", "/content")
|
||||||
|
staticDir := getenv("BLOG_STATIC_DIR", "/app/internal/web/static")
|
||||||
|
pagesDir := getenv("BLOG_PAGES_DIR", "/pages")
|
||||||
|
templatesDir := getenv("BLOG_TEMPLATES_DIR", "/templates")
|
||||||
|
ticksDir := getenv("BLOG_TICKS_DIR", "/ticks")
|
||||||
|
gitEnable := enabled("GIT_ENABLE", false)
|
||||||
|
gitRepo := getenv("GIT_REPO", "null")
|
||||||
|
gitBranch := getenv("GIT_BRANCH", "main")
|
||||||
|
gitDir := getenv("GIT_DIR", "/git-temp")
|
||||||
|
gitInterval := getenv("GIT_INTERVAL", "10")
|
||||||
|
|
||||||
|
TickCatalog = nil
|
||||||
|
if err := LoadTickCatalog(ticksDir + "/ticks.json"); err != nil {
|
||||||
|
fmt.Println("Fehler beim Laden:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Geladener Katalog:", TickCatalog)
|
||||||
|
|
||||||
funcs := template.FuncMap{
|
funcs := template.FuncMap{
|
||||||
"now": time.Now, // jetzt‑Zeit bereitstellen
|
"now": time.Now, // jetzt‑Zeit bereitstellen
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basislayout zuerst parsen
|
// Basislayout zuerst parsen
|
||||||
layout := template.Must(
|
layout := template.Must(template.New("base").Funcs(funcs).ParseFiles(templatesDir + "/base.html"))
|
||||||
template.New("base").Funcs(funcs).
|
|
||||||
ParseFiles("internal/web/templates/base.html"),
|
|
||||||
)
|
|
||||||
|
|
||||||
// LIST‑Seite: base + list.html
|
// LIST‑Seite: base + list.html
|
||||||
tplList = template.Must(layout.Clone())
|
tplList = template.Must(layout.Clone())
|
||||||
template.Must(tplList.Funcs(funcs).ParseFiles("internal/web/templates/list.html"))
|
template.Must(tplList.Funcs(funcs).ParseFiles(templatesDir + "/list.html"))
|
||||||
|
|
||||||
// ARTICLE‑Instanz
|
// ARTICLE‑Instanz
|
||||||
tplArticle = template.Must(layout.Clone())
|
tplArticle = template.Must(layout.Clone())
|
||||||
template.Must(tplArticle.Funcs(funcs).ParseFiles("internal/web/templates/article.html"))
|
template.Must(tplArticle.Funcs(funcs).ParseFiles(templatesDir + "/article.html"))
|
||||||
|
|
||||||
tplPage := template.Must(layout.Clone())
|
tplPage := template.Must(layout.Clone())
|
||||||
template.Must(tplPage.ParseFiles("internal/web/templates/page.html"))
|
template.Must(tplPage.ParseFiles(templatesDir + "/page.html"))
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
staticDir := "internal/web/static"
|
articles, err := article.LoadDir(contentDir)
|
||||||
|
|
||||||
articles, err := article.LoadDir("content")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
staticPages, err := article.LoadStatic("pages")
|
Xarticles = articles
|
||||||
|
|
||||||
|
staticPages, err := article.LoadStatic(pagesDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
} else {
|
} else {
|
||||||
@@ -84,15 +171,31 @@ func main() {
|
|||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := tplList.ExecuteTemplate(w, "layout", articles); err != nil {
|
|
||||||
|
/* */
|
||||||
|
|
||||||
|
for a, b := range Xarticles {
|
||||||
|
Xarticles[a].Counter = getTick(b.Slug)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* */
|
||||||
|
|
||||||
|
if err := tplList.ExecuteTemplate(w, "layout", article.ListPage{
|
||||||
|
Title: "Startseite",
|
||||||
|
Description: "Alle Artikel im Überblick",
|
||||||
|
Articles: Xarticles,
|
||||||
|
}); err != nil {
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), 500)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/post/", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/post/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
slug := strings.TrimPrefix(r.URL.Path, "/post/")
|
slug := strings.TrimPrefix(r.URL.Path, "/post/")
|
||||||
for _, a := range articles {
|
for _, a := range Xarticles {
|
||||||
if a.Slug == slug {
|
if a.Slug == slug {
|
||||||
|
IncTick(slug)
|
||||||
|
t := getTick(slug)
|
||||||
|
a.Counter = t
|
||||||
if err := tplArticle.ExecuteTemplate(w, "layout", a); err != nil {
|
if err := tplArticle.ExecuteTemplate(w, "layout", a); err != nil {
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), 500)
|
||||||
}
|
}
|
||||||
@@ -115,9 +218,188 @@ func main() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.Handle("/static/",
|
mux.Handle("/static/", cacheControl(http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir)))))
|
||||||
cacheControl(http.StripPrefix("/static/",
|
|
||||||
http.FileServer(http.Dir(staticDir)))))
|
|
||||||
|
|
||||||
http.ListenAndServe(":8080", mux)
|
if gitEnable {
|
||||||
|
xMinute, _ := strconv.Atoi(gitInterval)
|
||||||
|
xDuration := time.Duration(xMinute) * time.Minute
|
||||||
|
go startAutoClone(gitRepo, gitBranch, gitDir, xDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
StopServer(http.ListenAndServe(":8080", mux))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareExit() {
|
||||||
|
fmt.Println("~", "Running exit tasks...")
|
||||||
|
if err := SaveTickCatalog(getenv("BLOG_TICKS_DIR", "/ticks") + "/ticks.json"); err != nil {
|
||||||
|
fmt.Println("Fehler beim Speichern:", err)
|
||||||
|
}
|
||||||
|
fmt.Println("Geladener Katalog:", TickCatalog)
|
||||||
|
fmt.Println("~", "Exit completed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func StopServer(e error) {
|
||||||
|
fmt.Println("~", "Stopping server...")
|
||||||
|
prepareExit()
|
||||||
|
fmt.Println("~", "Server stopped!")
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneRepo(repoURL, branch, dir string) {
|
||||||
|
fmt.Printf("Starte Klonvorgang für Branch '%s'...\n", branch)
|
||||||
|
|
||||||
|
// Verzeichnis löschen
|
||||||
|
if err := os.RemoveAll(dir); err != nil {
|
||||||
|
fmt.Println("Fehler beim Löschen des Verzeichnisses:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Git-Clone mit Branch
|
||||||
|
cmd := exec.Command("git", "clone", "--branch", branch, "--single-branch", repoURL, dir)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
fmt.Println("Fehler beim Klonen:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Repo erfolgreich geklont.")
|
||||||
|
}
|
||||||
|
|
||||||
|
contentDir := getenv("BLOG_CONTENT_DIR", "/content")
|
||||||
|
|
||||||
|
err := os.RemoveAll("/content")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err, "/content")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll("/static")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err, "/static")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll("/pages")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err, "/pages")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll("/templates")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err, "/templates")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll("/content", 0755); err != nil {
|
||||||
|
fmt.Println("Fehler beim Erstellen des Zielordners:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll("/static", 0755); err != nil {
|
||||||
|
fmt.Println("Fehler beim Erstellen des Zielordners:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll("/pages", 0755); err != nil {
|
||||||
|
fmt.Println("Fehler beim Erstellen des Zielordners:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll("/templates", 0755); err != nil {
|
||||||
|
fmt.Println("Fehler beim Erstellen des Zielordners:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyDirContents("/git-temp/articles", "/content"); err != nil {
|
||||||
|
fmt.Println("Fehler beim Kopieren:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Kopieren abgeschlossen.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyDirContents("/git-temp/static", "/static"); err != nil {
|
||||||
|
fmt.Println("Fehler beim Kopieren:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Kopieren abgeschlossen.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyDirContents("/git-temp/pages", "/pages"); err != nil {
|
||||||
|
fmt.Println("Fehler beim Kopieren:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Kopieren abgeschlossen.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyDirContents("/git-temp/templates", "/templates"); err != nil {
|
||||||
|
fmt.Println("Fehler beim Kopieren:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Kopieren abgeschlossen.")
|
||||||
|
}
|
||||||
|
|
||||||
|
articles, err := article.LoadDir(contentDir)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Xarticles = articles
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDirContents(srcDir, destDir string) error {
|
||||||
|
entries, err := os.ReadDir(srcDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Fehler beim Lesen von %s: %w", srcDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
srcPath := filepath.Join(srcDir, entry.Name())
|
||||||
|
destPath := filepath.Join(destDir, entry.Name())
|
||||||
|
|
||||||
|
info, err := entry.Info()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
if err := os.MkdirAll(destPath, info.Mode()); err != nil {
|
||||||
|
return fmt.Errorf("Fehler beim Erstellen von Ordner %s: %w", destPath, err)
|
||||||
|
}
|
||||||
|
// rekursiv kopieren
|
||||||
|
if err := copyDirContents(srcPath, destPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := copyFile(srcPath, destPath, info); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(src, dest string, info os.FileInfo) error {
|
||||||
|
in, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
out, err := os.Create(dest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(out, in); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Chmod(dest, info.Mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
func startAutoClone(repoURL, branch, dir string, interval time.Duration) {
|
||||||
|
go cloneRepo(repoURL, branch, dir) // sofortiger Start
|
||||||
|
|
||||||
|
ticker := time.NewTicker(interval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for range ticker.C {
|
||||||
|
go cloneRepo(repoURL, branch, dir)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
<!--{"title": "ChatGPT + Go: So geht's", "date": "2025-05-04", "slug": "chatgpt-go", "cover": ""}-->
|
|
||||||
# Hello Gophers 🤖
|
|
||||||
*(Markdown‑Inhalt)*
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<!--{"title": "Mein erster Markdown‑Posty", "date": "2025-05-04", "slug": "mein-erster-markdown-posty", "cover": "/static/img/placeholder.png"}-->
|
|
||||||
|
|
||||||
# Hello Markdown 🌟
|
|
||||||
|
|
||||||
Dies ist *kursiv*, **fett** und ein [Link](https://example.com).
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<!--{"title": "Mein erster Markdown‑Post", "date": "2025-05-04", "slug": "mein-erster-markdown-post", "cover": "/static/img/placeholder.png"}-->
|
|
||||||
|
|
||||||
# Hello Markdown 🌟
|
|
||||||
|
|
||||||
Dies ist *kursiv*, **fett** und ein [Link](https://example.com).
|
|
||||||
@@ -62,6 +62,7 @@ func LoadDir(root string) ([]Article, error) {
|
|||||||
Date string `json:"date"`
|
Date string `json:"date"`
|
||||||
Slug string `json:"slug"`
|
Slug string `json:"slug"`
|
||||||
Cover string `json:"cover"`
|
Cover string `json:"cover"`
|
||||||
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(headerJSON, &meta); err != nil {
|
if err := json.Unmarshal(headerJSON, &meta); err != nil {
|
||||||
return fmt.Errorf("%s: %w", path, err)
|
return fmt.Errorf("%s: %w", path, err)
|
||||||
@@ -91,6 +92,7 @@ func LoadDir(root string) ([]Article, error) {
|
|||||||
Slug: meta.Slug,
|
Slug: meta.Slug,
|
||||||
Date: date,
|
Date: date,
|
||||||
Cover: meta.Cover,
|
Cover: meta.Cover,
|
||||||
|
Description: meta.Description,
|
||||||
Body: template.HTML(body),
|
Body: template.HTML(body),
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
@@ -107,6 +109,7 @@ func LoadDir(root string) ([]Article, error) {
|
|||||||
type StaticPage struct {
|
type StaticPage struct {
|
||||||
Title string
|
Title string
|
||||||
Slug string
|
Slug string
|
||||||
|
Description string
|
||||||
Body template.HTML
|
Body template.HTML
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +169,7 @@ func LoadStatic(dir string) (map[string]StaticPage, error) {
|
|||||||
var meta struct {
|
var meta struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Slug string `json:"slug"`
|
Slug string `json:"slug"`
|
||||||
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(headerJSON, &meta); err != nil {
|
if err := json.Unmarshal(headerJSON, &meta); err != nil {
|
||||||
return nil, fmt.Errorf("%s: %w", path, err)
|
return nil, fmt.Errorf("%s: %w", path, err)
|
||||||
@@ -187,6 +191,7 @@ func LoadStatic(dir string) (map[string]StaticPage, error) {
|
|||||||
pages[meta.Slug] = StaticPage{
|
pages[meta.Slug] = StaticPage{
|
||||||
Title: meta.Title,
|
Title: meta.Title,
|
||||||
Slug: meta.Slug,
|
Slug: meta.Slug,
|
||||||
|
Description: meta.Description,
|
||||||
Body: template.HTML(body),
|
Body: template.HTML(body),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,14 @@ type Article struct {
|
|||||||
Title string
|
Title string
|
||||||
Slug string
|
Slug string
|
||||||
Date time.Time
|
Date time.Time
|
||||||
Cover string // NEW
|
Cover string
|
||||||
Body template.HTML // already trusted
|
Body template.HTML
|
||||||
|
Description string
|
||||||
|
Counter string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListPage struct {
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
Articles []Article
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
internal/web/static/fonts/FiraCode-VF.woff2
Normal file
BIN
internal/web/static/fonts/FiraCode-VF.woff2
Normal file
Binary file not shown.
BIN
internal/web/static/fonts/InterVariable.woff2
Normal file
BIN
internal/web/static/fonts/InterVariable.woff2
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 5.0 MiB |
BIN
internal/web/static/img/favicon.ico
Normal file
BIN
internal/web/static/img/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 6.9 MiB |
BIN
internal/web/static/img/placeholder.webp
Normal file
BIN
internal/web/static/img/placeholder.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
@@ -1,11 +1,27 @@
|
|||||||
|
/* ---------- Local Web‑Fonts ---------- */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Fira Code";
|
||||||
|
src: url("/static/fonts/FiraCode-VariableFont.woff2") format("woff2");
|
||||||
|
font-weight: 400 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "Inter";
|
||||||
|
src: url("/static/fonts/Inter-VariableFont.woff2") format("woff2");
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- Farbpalette ---------- */
|
/* ---------- Farbpalette ---------- */
|
||||||
:root {
|
:root {
|
||||||
/* Light theme */
|
/* Light theme */
|
||||||
--bg: #f5f7fa;
|
--bg: #f5f7fa;
|
||||||
--bg-alt: #ffffff;
|
--bg-alt: #ffffff;
|
||||||
--card-bg: #ffffff;
|
--card-bg: #ffffff;
|
||||||
--text: #1f2933;
|
--text: #000000;
|
||||||
--text-muted: #64748b;
|
--text-muted: #1f2933;
|
||||||
--accent: #2563eb; /* Indigo‑600 */
|
--accent: #2563eb; /* Indigo‑600 */
|
||||||
--accent-light: #3b82f6; /* Indigo‑500 */
|
--accent-light: #3b82f6; /* Indigo‑500 */
|
||||||
--code-bg: #f1f5f9;
|
--code-bg: #f1f5f9;
|
||||||
@@ -14,7 +30,7 @@
|
|||||||
--gap: 2rem;
|
--gap: 2rem;
|
||||||
--shadow: 0 4px 16px rgba(0,0,0,.08);
|
--shadow: 0 4px 16px rgba(0,0,0,.08);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-family: "Inter", system-ui, "Segoe UI", Roboto, sans-serif;
|
font-family: "Inter", system-ui, sans-serif;
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,8 +40,8 @@
|
|||||||
--bg: #0d1117;
|
--bg: #0d1117;
|
||||||
--bg-alt: #161b22;
|
--bg-alt: #161b22;
|
||||||
--card-bg: #161b22;
|
--card-bg: #161b22;
|
||||||
--text: #c9d1d9;
|
--text: #ffffff;
|
||||||
--text-muted: #8b949e;
|
--text-muted: #e4e8ec;
|
||||||
--accent: #3b82f6;
|
--accent: #3b82f6;
|
||||||
--accent-light:#60a5fa;
|
--accent-light:#60a5fa;
|
||||||
--code-bg: #1e242c;
|
--code-bg: #1e242c;
|
||||||
@@ -44,11 +60,15 @@ body {
|
|||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
text-decoration: none;
|
text-decoration: underline dotted;
|
||||||
transition: color .15s;
|
transition: color .15s;
|
||||||
}
|
}
|
||||||
a:hover { color: var(--accent-light); }
|
a:hover { color: var(--accent-light); }
|
||||||
|
|
||||||
|
a.no-underline {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Container in der Mitte */
|
/* Container in der Mitte */
|
||||||
.wrapper {
|
.wrapper {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
@@ -56,6 +76,13 @@ a:hover { color: var(--accent-light); }
|
|||||||
padding: var(--gap) calc(var(--gap) / 1.5);
|
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ß ---------- */
|
/* ---------- Kopf & Fuß ---------- */
|
||||||
header, footer {
|
header, footer {
|
||||||
background: var(--bg-alt);
|
background: var(--bg-alt);
|
||||||
@@ -64,7 +91,7 @@ header, footer {
|
|||||||
header {
|
header {
|
||||||
position: sticky; top: 0; z-index: 10;
|
position: sticky; top: 0; z-index: 10;
|
||||||
display: flex; justify-content: space-between; align-items: center;
|
display: flex; justify-content: space-between; align-items: center;
|
||||||
padding: 1rem calc(var(--gap) / 1.2);
|
padding: 10px calc(var(--gap) / 1.2);
|
||||||
}
|
}
|
||||||
header h1 { margin: 0; font-size: 1.4rem; }
|
header h1 { margin: 0; font-size: 1.4rem; }
|
||||||
footer {
|
footer {
|
||||||
@@ -128,6 +155,10 @@ article blockquote {
|
|||||||
background: var(--code-bg);
|
background: var(--code-bg);
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
code {
|
||||||
|
background: var(--code-bg);
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
.main-nav ul {
|
.main-nav ul {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1.25rem;
|
gap: 1.25rem;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
{{ if .Cover }}
|
{{ if .Cover }}
|
||||||
<img class="hero" src="{{ .Cover }}" alt="">
|
<img class="hero" src="{{ .Cover }}" alt="">
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
<p><a class="no-underline" href="/">← Zurück zur Übersicht</a></p>
|
||||||
<h1>{{ .Title }}</h1>
|
<h1>{{ .Title }}</h1>
|
||||||
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "02.01.2006" }}</time>
|
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "02.01.2006" }}</time>
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
{{ .Body }}
|
{{ .Body }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p><a href="/">← Zurück zur Übersicht</a></p>
|
<p><a class="no-underline" href="/">← Zurück zur Übersicht</a></p>
|
||||||
</article>
|
</article>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
|||||||
@@ -4,22 +4,23 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
{{ if .Description }}
|
||||||
|
<meta name="description" content="{{ .Description }}">
|
||||||
|
{{ end }}
|
||||||
<title>{{ block "title" . }}B1tsblog{{ end }}</title>
|
<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">
|
<link rel="stylesheet" href="/static/main.css">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
|
||||||
<!-- Monospace‑Font für Code (optional) -->
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<h1><a href="/">B1tsblog</a></h1>
|
<h1><a href="/" class="no-underline">B1tsblog</a></h1>
|
||||||
<nav class="main-nav">
|
<nav class="main-nav">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/">Startseite</a></li>
|
<li><a class="no-underline" href="/">Start</a></li>
|
||||||
<li><a href="/page/impressum">Impressum</a></li>
|
<li><a class="no-underline" href="/page/welcome">Hallo</a></li>
|
||||||
<!-- später: <li><a href="/page/datenschutz">Datenschutz</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>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
@@ -29,7 +30,9 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="wrapper">
|
<footer class="wrapper">
|
||||||
© {{ now.Year }} · B1tK1ll3r
|
© {{ now.Year }} · Jan Bergner / B1tsBlog
|
||||||
|
<hr>
|
||||||
|
Ich verzichte auf Cookies, affiliate Links, Tracking und die Einbindung von Drittanbieter-Diensten.
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{{ define "body" }}
|
{{ define "body" }}
|
||||||
<ul class="post-list">
|
<ul class="post-list">
|
||||||
{{ range . }}
|
{{ range .Articles }}
|
||||||
<li>
|
<li>
|
||||||
<a class="card" href="/post/{{ .Slug }}">
|
<a class="card no-underline" href="/post/{{ .Slug }}">
|
||||||
{{ if .Cover }}
|
{{ if .Cover }}
|
||||||
<img src="{{ .Cover }}" alt="">
|
<img src="{{ .Cover }}" alt="">
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<img src="/static/img/placeholder.png" alt="">
|
<img src="/static/img/placeholder.webp" alt="">
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<h2>{{ .Title }}</h2>
|
<h2>{{ .Title }}</h2>
|
||||||
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "02.01.2006" }}</time>
|
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "02.01.2006" }}</time> ({{ .Counter }})
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
{{ define "body" }}
|
{{ define "body" }}
|
||||||
<article>
|
<article>
|
||||||
|
<p><a class="no-underline" href="/">← Zurück</a></p>
|
||||||
<h1>{{ .Title }}</h1>
|
<h1>{{ .Title }}</h1>
|
||||||
<div class="article-content">{{ .Body }}</div>
|
<div class="article-content">{{ .Body }}</div>
|
||||||
<p><a href="/">← Zurück</a></p>
|
<p><a class="no-underline" href="/">← Zurück</a></p>
|
||||||
</article>
|
</article>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user