mirror of
https://github.com/skoelle/moonweb-site.git
synced 2026-09-17 17:00:25 +00:00
Consolidate all moonweb sites into single Eleventy config
- Single eleventy.config.js at root with computed pathPrefix per section - Accent colors inlined in base.njk (no per-site theme.css needed) - Hub pages output at root via explicit permalinks - Per-section sitemaps with tag-based collections - .eleventyignore excludes stefankoelle, timecapsule, markdown files - Deleted: 5 per-site configs, merge script - Simplified: package.json (single build/dev commands) - Simplified: GitHub Actions workflow (no matrix, no merge job) - Fixed: parent links for sub-site detail pages
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
stefankoelle/
|
||||
timecapsule/
|
||||
scripts/
|
||||
.tmp/
|
||||
|
||||
# Root markdown files (not templates)
|
||||
/*.md
|
||||
|
||||
# Hub static redirect files (handled by passthrough copy)
|
||||
hub/hardware.html
|
||||
hub/users.html
|
||||
hub/index2.html
|
||||
hub/index3.html
|
||||
hub/new.html
|
||||
hub/links.html
|
||||
hub/infos.html
|
||||
hub/start.html
|
||||
@@ -21,12 +21,8 @@ env:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ matrix.site }}
|
||||
name: Build moonweb sites
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
site: [hub, infra, smarthome, code, retro]
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
@@ -38,118 +34,45 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build ${{ matrix.site }}
|
||||
run: npm run build:${{ matrix.site }}
|
||||
|
||||
- name: Validate build output
|
||||
run: |
|
||||
if [ ! -d "dist/${{ matrix.site }}" ]; then
|
||||
echo "Error: dist/${{ matrix.site }} not found"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$(ls -A dist/${{ matrix.site }} 2>/dev/null)" ]; then
|
||||
echo "Error: dist/${{ matrix.site }} is empty"
|
||||
exit 1
|
||||
fi
|
||||
echo "Build output:"
|
||||
find dist/${{ matrix.site }} -type f | head -20
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: dist-${{ matrix.site }}
|
||||
path: dist/${{ matrix.site }}
|
||||
retention-days: 7
|
||||
|
||||
build-timecapsule:
|
||||
name: Build timecapsule
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install timecapsule dependencies
|
||||
run: cd timecapsule && npm ci
|
||||
- name: Build all moonweb sites
|
||||
run: npm run build
|
||||
|
||||
- name: Build timecapsule
|
||||
run: npm run build:timecapsule
|
||||
|
||||
- name: Validate build output
|
||||
run: |
|
||||
if [ ! -d "dist/timecapsule" ]; then
|
||||
echo "Error: dist/timecapsule not found"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$(ls -A dist/timecapsule 2>/dev/null)" ]; then
|
||||
echo "Error: dist/timecapsule is empty"
|
||||
exit 1
|
||||
fi
|
||||
echo "Build output:"
|
||||
find dist/timecapsule -type f | head -20
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: dist-timecapsule
|
||||
path: dist/timecapsule
|
||||
retention-days: 7
|
||||
|
||||
merge:
|
||||
name: Merge all sites
|
||||
needs: [build, build-timecapsule]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: artifacts/
|
||||
|
||||
- name: Merge into dist/
|
||||
run: |
|
||||
mkdir -p dist
|
||||
|
||||
# Hub becomes root
|
||||
cp -r artifacts/dist-hub/* dist/
|
||||
|
||||
# Subdirectories
|
||||
for site in infra smarthome code retro timecapsule; do
|
||||
if [ -d "artifacts/dist-$site" ]; then
|
||||
cp -r "artifacts/dist-$site" "dist/$site"
|
||||
fi
|
||||
done
|
||||
|
||||
# Copy shared CSS to root (accessible from all subdirectories)
|
||||
cp shared/base.css dist/shared-base.css
|
||||
cp shared/favicon/hub.svg dist/favicon.svg
|
||||
|
||||
echo "Final structure:"
|
||||
find dist -maxdepth 2 -type f | sort | head -40
|
||||
echo "..."
|
||||
echo "Total files:"
|
||||
find dist -type f | wc -l
|
||||
|
||||
- name: Upload merged artifact
|
||||
# Verify key directories exist
|
||||
for dir in "" infra smarthome code retro timecapsule; do
|
||||
if [ -n "$dir" ] && [ ! -d "dist/$dir" ]; then
|
||||
echo "Error: dist/$dir not found"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: dist-moonweb-merged
|
||||
name: dist-moonweb
|
||||
path: dist/
|
||||
retention-days: 7
|
||||
|
||||
deploy:
|
||||
name: Deploy to IONOS
|
||||
needs: merge
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
steps:
|
||||
- name: Download merged artifact
|
||||
- name: Download build artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: dist-moonweb-merged
|
||||
name: dist-moonweb
|
||||
path: dist/
|
||||
|
||||
- name: Deploy via SFTP
|
||||
|
||||
+2
-1
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: "Page Not Found"
|
||||
section: "code"
|
||||
tags: "code"
|
||||
description: "The page you are looking for does not exist."
|
||||
layout: base.njk
|
||||
permalink: /404.html
|
||||
permalink: /code/404.html
|
||||
---
|
||||
<div class="error-page">
|
||||
<h1>404</h1>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
module.exports = function (eleventyConfig) {
|
||||
eleventyConfig.addGlobalData("site", { url: "https://www.moonweb.org", pathPrefix: "/code" });
|
||||
eleventyConfig.addFilter("date", (d) => d.toISOString());
|
||||
eleventyConfig.addPassthroughCopy({ "shared/theme-code.css": "theme.css" });
|
||||
eleventyConfig.addPassthroughCopy({ "shared/base.css": "shared-base.css" });
|
||||
eleventyConfig.addPassthroughCopy({ "shared/favicon/code.svg": "favicon.svg" });
|
||||
eleventyConfig.addPassthroughCopy({ "code/robots.txt": "robots.txt" });
|
||||
|
||||
return {
|
||||
dir: {
|
||||
input: "code",
|
||||
includes: "../shared/_includes",
|
||||
output: "dist/code"
|
||||
},
|
||||
serverOptions: {
|
||||
host: "0.0.0.0",
|
||||
port: 8084
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: "Open Source Projects — GitHub Repos & Code | moonweb"
|
||||
section: "code"
|
||||
tags: "code"
|
||||
description: "Curated catalog of my public GitHub projects — automation, dev tools, web apps, and firmware."
|
||||
layout: base.njk
|
||||
subcategories:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
permalink: /sitemap.xml
|
||||
permalink: /code/sitemap.xml
|
||||
sitemapCollection: "code"
|
||||
eleventyExcludeFromCollections: true
|
||||
---
|
||||
{% include "sitemap.njk" %}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
const SITES = {
|
||||
hub: { pathPrefix: "" },
|
||||
infra: { pathPrefix: "/infra" },
|
||||
smarthome: { pathPrefix: "/smarthome" },
|
||||
code: { pathPrefix: "/code" },
|
||||
retro: { pathPrefix: "/retro" },
|
||||
};
|
||||
|
||||
module.exports = function (eleventyConfig) {
|
||||
eleventyConfig.addGlobalData("site", { url: "https://www.moonweb.org" });
|
||||
eleventyConfig.addFilter("date", (d) => d.toISOString());
|
||||
|
||||
// Computed pathPrefix based on page's section front matter
|
||||
eleventyConfig.addGlobalData("eleventyComputed", {
|
||||
pathPrefix: (data) => SITES[data.section]?.pathPrefix ?? "",
|
||||
});
|
||||
|
||||
// Per-section collections (for sitemaps)
|
||||
for (const section of Object.keys(SITES)) {
|
||||
eleventyConfig.addCollection(section, (collectionsApi) =>
|
||||
collectionsApi.getFilteredByTag(section)
|
||||
);
|
||||
}
|
||||
|
||||
// --- Passthrough copies ---
|
||||
|
||||
// Root-level shared assets
|
||||
eleventyConfig.addPassthroughCopy({ "shared/base.css": "shared-base.css" });
|
||||
eleventyConfig.addPassthroughCopy({ "shared/favicon/hub.svg": "favicon.svg" });
|
||||
|
||||
// Per-section assets: favicon, robots.txt
|
||||
for (const [section, cfg] of Object.entries(SITES)) {
|
||||
const prefix = cfg.pathPrefix || ".";
|
||||
eleventyConfig.addPassthroughCopy({
|
||||
[`shared/favicon/${section}.svg`]: `${prefix}/favicon.svg`,
|
||||
});
|
||||
eleventyConfig.addPassthroughCopy({
|
||||
[`${section}/robots.txt`]: `${prefix}/robots.txt`,
|
||||
});
|
||||
}
|
||||
|
||||
// Hub: profile photo
|
||||
eleventyConfig.addPassthroughCopy({
|
||||
"hub/stefan-koelle-foto.jpg": "stefan-koelle-foto.jpg",
|
||||
});
|
||||
|
||||
// Hub: old www.moonweb.org redirects (.htm → .html)
|
||||
const htmRedirects = [
|
||||
"start",
|
||||
"index2",
|
||||
"index3",
|
||||
"new",
|
||||
"links",
|
||||
"infos",
|
||||
"hardware",
|
||||
"users",
|
||||
];
|
||||
for (const name of htmRedirects) {
|
||||
eleventyConfig.addPassthroughCopy({
|
||||
[`hub/${name}.htm`]: `${name}.html`,
|
||||
});
|
||||
}
|
||||
const htmSubdirs = [
|
||||
"beginning",
|
||||
"projects",
|
||||
"products",
|
||||
"acoustics",
|
||||
"location",
|
||||
"background",
|
||||
"partners",
|
||||
"contact",
|
||||
"network",
|
||||
"stefan",
|
||||
"legal-notice",
|
||||
];
|
||||
for (const dir of htmSubdirs) {
|
||||
eleventyConfig.addPassthroughCopy({
|
||||
[`hub/${dir}/index.htm`]: `${dir}/index.html`,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
dir: {
|
||||
input: ".",
|
||||
includes: "shared/_includes",
|
||||
output: "dist",
|
||||
},
|
||||
serverOptions: {
|
||||
host: "0.0.0.0",
|
||||
port: 8081,
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: "Page Not Found"
|
||||
section: "hub"
|
||||
tags: "hub"
|
||||
description: "The page you are looking for does not exist."
|
||||
layout: base.njk
|
||||
permalink: /404.html
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
module.exports = function (eleventyConfig) {
|
||||
eleventyConfig.addGlobalData("site", { url: "https://www.moonweb.org" });
|
||||
eleventyConfig.addFilter("date", (d) => d.toISOString());
|
||||
eleventyConfig.addPassthroughCopy({ "shared/theme-hub.css": "theme.css" });
|
||||
eleventyConfig.addPassthroughCopy({ "shared/base.css": "shared-base.css" });
|
||||
eleventyConfig.addPassthroughCopy({ "shared/favicon/hub.svg": "favicon.svg" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/robots.txt": "robots.txt" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/stefan-koelle-foto.jpg": "stefan-koelle-foto.jpg" });
|
||||
|
||||
// Old www.moonweb.org redirects (root-level HTML shortcuts)
|
||||
// .htm files are excluded from Eleventy processing, renamed to .html on output
|
||||
eleventyConfig.addPassthroughCopy({ "hub/start.htm": "start.html" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/index2.htm": "index2.html" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/index3.htm": "index3.html" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/new.htm": "new.html" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/links.htm": "links.html" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/infos.htm": "infos.html" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/hardware.htm": "hardware.html" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/users.htm": "users.html" });
|
||||
|
||||
// Old www.moonweb.org redirects (content subdirectories)
|
||||
// .htm files are excluded from Eleventy processing, renamed to .html on output
|
||||
eleventyConfig.addPassthroughCopy({ "hub/beginning/index.htm": "beginning/index.html" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/projects/index.htm": "projects/index.html" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/products/index.htm": "products/index.html" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/acoustics/index.htm": "acoustics/index.html" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/location/index.htm": "location/index.html" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/background/index.htm": "background/index.html" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/partners/index.htm": "partners/index.html" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/contact/index.htm": "contact/index.html" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/network/index.htm": "network/index.html" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/stefan/index.htm": "stefan/index.html" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/legal-notice/index.htm": "legal-notice/index.html" });
|
||||
|
||||
return {
|
||||
dir: {
|
||||
input: "hub",
|
||||
includes: "../shared/_includes",
|
||||
output: "dist/hub"
|
||||
},
|
||||
serverOptions: {
|
||||
host: "0.0.0.0",
|
||||
port: 8081
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: "Legal Notice & Privacy Policy"
|
||||
section: "hub"
|
||||
tags: "hub"
|
||||
permalink: /impressum/index.html
|
||||
description: "Legal notice (Impressum) and privacy policy for moonweb.org."
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: "Stefan Koelle — Homelab, Smart Home, Code & Retro Hardware | moonweb"
|
||||
section: "hub"
|
||||
tags: "hub"
|
||||
permalink: /index.html
|
||||
description: "Stefan Koelle — Senior Software Developer & Architect based in Munich. Homelab projects, smart home automation, open-source code, and retro hardware."
|
||||
layout: base.njk
|
||||
ogImage: "/stefan-koelle-foto.jpg"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
permalink: /sitemap.xml
|
||||
sitemapCollection: "hub"
|
||||
eleventyExcludeFromCollections: true
|
||||
---
|
||||
{% include "sitemap.njk" %}
|
||||
|
||||
+2
-1
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: "Page Not Found"
|
||||
section: "infra"
|
||||
tags: "infra"
|
||||
description: "The page you are looking for does not exist."
|
||||
layout: base.njk
|
||||
permalink: /404.html
|
||||
permalink: /infra/404.html
|
||||
---
|
||||
<div class="error-page">
|
||||
<h1>404</h1>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "Backup Strategy"
|
||||
section: "infra"
|
||||
parent: "/"
|
||||
tags: "infra"
|
||||
parent: "/infra/"
|
||||
description: "Layered 3-2-1 backup strategy covering NAS snapshots, offsite copies, and cloud storage."
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "Xubuntu Dev VM"
|
||||
section: "infra"
|
||||
parent: "/"
|
||||
tags: "infra"
|
||||
parent: "/infra/"
|
||||
description: "A disposable, daily-backed-up Xubuntu VM for AI-assisted, remote development via SSH and Chrome Remote Desktop."
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "Docker Container Strategy"
|
||||
section: "infra"
|
||||
parent: "/"
|
||||
tags: "infra"
|
||||
parent: "/infra/"
|
||||
description: "How self-hosted services are organized across three Docker hosts on Proxmox and Synology, using Docker Compose as the single source of truth."
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
module.exports = function (eleventyConfig) {
|
||||
eleventyConfig.addGlobalData("site", { url: "https://www.moonweb.org", pathPrefix: "/infra" });
|
||||
eleventyConfig.addFilter("date", (d) => d.toISOString());
|
||||
eleventyConfig.addPassthroughCopy({ "shared/theme-infra.css": "theme.css" });
|
||||
eleventyConfig.addPassthroughCopy({ "shared/base.css": "shared-base.css" });
|
||||
eleventyConfig.addPassthroughCopy({ "shared/favicon/infra.svg": "favicon.svg" });
|
||||
eleventyConfig.addPassthroughCopy({ "infra/robots.txt": "robots.txt" });
|
||||
|
||||
return {
|
||||
dir: {
|
||||
input: "infra",
|
||||
includes: "../shared/_includes",
|
||||
output: "dist/infra"
|
||||
},
|
||||
serverOptions: {
|
||||
host: "0.0.0.0",
|
||||
port: 8082
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: "Homelab Infrastructure — Proxmox, Docker & Networking | moonweb"
|
||||
section: "infra"
|
||||
tags: "infra"
|
||||
description: "Homelab infrastructure overview: Proxmox hosts, Synology NAS, Docker VMs and Setup-Guides."
|
||||
layout: base.njk
|
||||
sections:
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "LXC Container Strategy"
|
||||
section: "infra"
|
||||
parent: "/"
|
||||
tags: "infra"
|
||||
parent: "/infra/"
|
||||
description: "Overview of the lightweight LXC containers running on the main Proxmox host, and why they are LXC instead of Docker."
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "Monitoring"
|
||||
section: "infra"
|
||||
parent: "/"
|
||||
tags: "infra"
|
||||
parent: "/infra/"
|
||||
description: "Prometheus and Grafana monitoring stack for hosts, containers, and hardware metrics."
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "OpenWrt Multi-WAN Router"
|
||||
section: "infra"
|
||||
parent: "/"
|
||||
tags: "infra"
|
||||
parent: "/infra/"
|
||||
description: "Multi-WAN OpenWrt router in the study with WiFi and Powerline failover, plus VLAN 189 for IoT isolation."
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "Proxmox Virtualization"
|
||||
section: "infra"
|
||||
parent: "/"
|
||||
tags: "infra"
|
||||
parent: "/infra/"
|
||||
description: "What Proxmox VE is, the hardware it runs on in my homelab, its key advantages, and a short setup walkthrough."
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
permalink: /sitemap.xml
|
||||
permalink: /infra/sitemap.xml
|
||||
sitemapCollection: "infra"
|
||||
eleventyExcludeFromCollections: true
|
||||
---
|
||||
{% include "sitemap.njk" %}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "Synology NAS"
|
||||
section: "infra"
|
||||
parent: "/"
|
||||
tags: "infra"
|
||||
parent: "/infra/"
|
||||
description: "Model, storage setup and current role of the Synology NAS in the homelab after the late-2025 cleanup."
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
+3
-14
@@ -4,24 +4,13 @@
|
||||
"private": true,
|
||||
"description": "Monorepo for hub/infra/smarthome/code/retro/timecapsule (Eleventy, static, deployed to IONOS SFTP)",
|
||||
"scripts": {
|
||||
"dev:hub": "npx @11ty/eleventy --config=hub/eleventy.config.js --serve --port=8081",
|
||||
"dev:infra": "npx @11ty/eleventy --config=infra/eleventy.config.js --serve --port=8082",
|
||||
"dev:smarthome": "npx @11ty/eleventy --config=smarthome/eleventy.config.js --serve --port=8083",
|
||||
"dev:code": "npx @11ty/eleventy --config=code/eleventy.config.js --serve --port=8084",
|
||||
"dev:retro": "npx @11ty/eleventy --config=retro/eleventy.config.js --serve --port=8085",
|
||||
"dev": "npx @11ty/eleventy --serve --port=8081",
|
||||
"dev:stefankoelle": "npx @11ty/eleventy --config=stefankoelle/eleventy.config.js --serve --port=8086",
|
||||
"dev:timecapsule": "cd timecapsule && npx @11ty/eleventy --serve --port=8087",
|
||||
"dev": "npm run dev:hub & npm run dev:infra & npm run dev:smarthome & npm run dev:code & npm run dev:retro & npm run dev:stefankoelle & npm run dev:timecapsule",
|
||||
"build:hub": "npx @11ty/eleventy --config=hub/eleventy.config.js",
|
||||
"build:infra": "npx @11ty/eleventy --config=infra/eleventy.config.js",
|
||||
"build:smarthome": "npx @11ty/eleventy --config=smarthome/eleventy.config.js",
|
||||
"build:code": "npx @11ty/eleventy --config=code/eleventy.config.js",
|
||||
"build:retro": "npx @11ty/eleventy --config=retro/eleventy.config.js",
|
||||
"build": "npx @11ty/eleventy",
|
||||
"build:stefankoelle": "npx @11ty/eleventy --config=stefankoelle/eleventy.config.js",
|
||||
"build:timecapsule": "cd timecapsule && npx @11ty/eleventy && mkdir -p ../../dist/timecapsule && cp -r _site/timecapsule/* ../../dist/timecapsule/",
|
||||
"build": "npm run build:hub && npm run build:infra && npm run build:smarthome && npm run build:code && npm run build:retro && npm run build:timecapsule && npm run build:stefankoelle",
|
||||
"build:moonweb": "npm run build:hub && npm run build:infra && npm run build:smarthome && npm run build:code && npm run build:retro && npm run build:timecapsule",
|
||||
"merge:moonweb": "bash scripts/merge-moonweb.sh",
|
||||
"build:moonweb": "npm run build && npm run build:timecapsule",
|
||||
"pdf:cv": "bash stefankoelle/pdf/build-pdf.sh",
|
||||
"prebuild": "bash prebuild.sh"
|
||||
},
|
||||
|
||||
+2
-1
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: "Page Not Found"
|
||||
section: "retro"
|
||||
tags: "retro"
|
||||
description: "The page you are looking for does not exist."
|
||||
layout: base.njk
|
||||
permalink: /404.html
|
||||
permalink: /retro/404.html
|
||||
---
|
||||
<div class="error-page">
|
||||
<h1>404</h1>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: "Running AmigaOS in 2026: MiSTer FPGA vs Amiberry on Pi"
|
||||
section: "retro"
|
||||
tags: "retro"
|
||||
description: "Two very different ways to get a fully working AmigaOS 3.2.3 desktop running today."
|
||||
layout: base.njk
|
||||
parent: "/"
|
||||
parent: "/retro/"
|
||||
---
|
||||
<h1>Running AmigaOS in 2026: MiSTer FPGA vs Amiberry on Pi</h1>
|
||||
<div class="detail-content">
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: "Vampire V4, A600GS or MiSTer: Amiga Hardware Isn't a Simple Speed Race"
|
||||
section: "retro"
|
||||
tags: "retro"
|
||||
description: "Why raw benchmark numbers are actively misleading when comparing modern Amiga hardware options."
|
||||
layout: base.njk
|
||||
parent: "/"
|
||||
parent: "/retro/"
|
||||
---
|
||||
<h1>Vampire V4, A600GS or MiSTer: Amiga Hardware Isn't a Simple Speed Race</h1>
|
||||
<div class="detail-content">
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: "The Atari Mega ST Fleet: Three Machines, Three Jobs"
|
||||
section: "retro"
|
||||
tags: "retro"
|
||||
description: "How three Atari Mega ST computers ended up with completely different roles in a modern retro setup."
|
||||
layout: base.njk
|
||||
parent: "/"
|
||||
parent: "/retro/"
|
||||
---
|
||||
<h1>The Atari Mega ST Fleet: Three Machines, Three Jobs</h1>
|
||||
<div class="detail-content">
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: "The 15kHz Problem: Finding a Monitor That Speaks Atari"
|
||||
section: "retro"
|
||||
tags: "retro"
|
||||
description: "Why most modern monitors refuse to display Atari ST colour modes, and which ones actually work."
|
||||
layout: base.njk
|
||||
parent: "/"
|
||||
parent: "/retro/"
|
||||
---
|
||||
<h1>The 15kHz Problem: Finding a Monitor That Speaks Atari</h1>
|
||||
<div class="detail-content">
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: "Getting a 40-Year-Old Computer Online: Atari ST Networking and BBS Access"
|
||||
section: "retro"
|
||||
tags: "retro"
|
||||
description: "Connecting an Atari ST to the modern internet and to dial-up-era bulletin board systems, warts and all."
|
||||
layout: base.njk
|
||||
parent: "/"
|
||||
parent: "/retro/"
|
||||
---
|
||||
<h1>Getting a 40-Year-Old Computer Online: Atari ST Networking and BBS Access</h1>
|
||||
<div class="detail-content">
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: "Batocera in the Living Room: One Box, Every Retro System"
|
||||
section: "retro"
|
||||
tags: "retro"
|
||||
description: "Why the living room emulation station takes a completely different approach than the FPGA rigs upstairs."
|
||||
layout: base.njk
|
||||
parent: "/"
|
||||
parent: "/retro/"
|
||||
---
|
||||
<h1>Batocera in the Living Room: One Box, Every Retro System</h1>
|
||||
<div class="detail-content">
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: "The 486 DX2-66: Rebuilding a DOS Era on MiSTer FPGA"
|
||||
section: "retro"
|
||||
tags: "retro"
|
||||
description: "Recreating a Vobis-Highscreen 486 DX2-66 setup on MiSTer FPGA, down to the original DOS configuration."
|
||||
layout: base.njk
|
||||
parent: "/"
|
||||
parent: "/retro/"
|
||||
---
|
||||
<h1>The 486 DX2-66: Rebuilding a DOS Era on MiSTer FPGA</h1>
|
||||
<div class="detail-content">
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
module.exports = function (eleventyConfig) {
|
||||
eleventyConfig.addGlobalData("site", { url: "https://www.moonweb.org", pathPrefix: "/retro" });
|
||||
eleventyConfig.addFilter("date", (d) => d.toISOString());
|
||||
eleventyConfig.addPassthroughCopy({ "shared/theme-retro.css": "theme.css" });
|
||||
eleventyConfig.addPassthroughCopy({ "shared/base.css": "shared-base.css" });
|
||||
eleventyConfig.addPassthroughCopy({ "shared/favicon/retro.svg": "favicon.svg" });
|
||||
eleventyConfig.addPassthroughCopy({ "retro/robots.txt": "robots.txt" });
|
||||
|
||||
return {
|
||||
dir: {
|
||||
input: "retro",
|
||||
includes: "../shared/_includes",
|
||||
output: "dist/retro"
|
||||
},
|
||||
serverOptions: {
|
||||
host: "0.0.0.0",
|
||||
port: 8085
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: "Why an ET4000 Graphics Card and a Mega ST Don't Mix (Yet)"
|
||||
section: "retro"
|
||||
tags: "retro"
|
||||
description: "A cautionary tale about pushing 1980s power supplies past their limits with a modern-ish graphics card."
|
||||
layout: base.njk
|
||||
parent: "/"
|
||||
parent: "/retro/"
|
||||
---
|
||||
<h1>Why an ET4000 Graphics Card and a Mega ST Don't Mix (Yet)</h1>
|
||||
<div class="detail-content">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: "Retro Computing — Vintage Hardware & FPGA | moonweb"
|
||||
section: "retro"
|
||||
tags: "retro"
|
||||
description: "Physical retro hardware collection — Raspberry Pi units, Pi 400, and vintage computing setups."
|
||||
layout: base.njk
|
||||
sections:
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: "Mini Arcades and Handhelds: Sorting the Real Deal from the Bootlegs"
|
||||
section: "retro"
|
||||
tags: "retro"
|
||||
description: "A buyer's guide to compact retro gaming devices, and why the cheapest option is almost never the right one."
|
||||
layout: base.njk
|
||||
parent: "/"
|
||||
parent: "/retro/"
|
||||
---
|
||||
<h1>Mini Arcades and Handhelds: Sorting the Real Deal from the Bootlegs</h1>
|
||||
<div class="detail-content">
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: "MiST and MiSTer: Two FPGA Boxes, Two Very Different Personalities"
|
||||
section: "retro"
|
||||
tags: "retro"
|
||||
description: "Why the newer, more powerful MiSTer isn't automatically the better choice for every retro system."
|
||||
layout: base.njk
|
||||
parent: "/"
|
||||
parent: "/retro/"
|
||||
---
|
||||
<h1>MiST and MiSTer: Two FPGA Boxes, Two Very Different Personalities</h1>
|
||||
<div class="detail-content">
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: "PiMiga 3 to 5: Chasing the Perfect Amiga-on-a-Pi Distro"
|
||||
section: "retro"
|
||||
tags: "retro"
|
||||
description: "Three generations of PiMiga later, here's what actually changed — and why newer isn't always better."
|
||||
layout: base.njk
|
||||
parent: "/"
|
||||
parent: "/retro/"
|
||||
---
|
||||
<h1>PiMiga 3 to 5: Chasing the Perfect Amiga-on-a-Pi Distro</h1>
|
||||
<div class="detail-content">
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: "The Retro Corner Today: A Snapshot of the Whole Setup"
|
||||
section: "retro"
|
||||
tags: "retro"
|
||||
description: "A tour through every retro system currently active across the home, room by room."
|
||||
layout: base.njk
|
||||
parent: "/"
|
||||
parent: "/retro/"
|
||||
---
|
||||
<h1>The Retro Corner Today: A Snapshot of the Whole Setup</h1>
|
||||
<div class="detail-content">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
permalink: /sitemap.xml
|
||||
permalink: /retro/sitemap.xml
|
||||
sitemapCollection: "retro"
|
||||
eleventyExcludeFromCollections: true
|
||||
---
|
||||
{% include "sitemap.njk" %}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: "The A1200: What We Know Before It Ships"
|
||||
section: "retro"
|
||||
tags: "retro"
|
||||
description: "A close look at Retro Games Ltd.'s upcoming Amiga 1200 console, based on everything public so far."
|
||||
layout: base.njk
|
||||
parent: "/"
|
||||
parent: "/retro/"
|
||||
---
|
||||
<h1>The A1200: What We Know Before It Ships</h1>
|
||||
<div class="detail-content">
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Merging moonweb sites for deployment..."
|
||||
|
||||
# Clean previous merge
|
||||
rm -rf dist/root
|
||||
|
||||
# Hub becomes root
|
||||
echo " hub → dist/ (root)"
|
||||
cp -r dist/hub dist/root
|
||||
rm -rf dist/hub
|
||||
|
||||
# Copy timecapsule from its build output
|
||||
if [ -d "timecapsule/_site/timecapsule" ]; then
|
||||
echo " timecapsule → dist/root/timecapsule/"
|
||||
cp -r timecapsule/_site/timecapsule dist/root/timecapsule
|
||||
else
|
||||
echo " WARNING: timecapsule/_site/timecapsule not found, skipping"
|
||||
fi
|
||||
|
||||
# Subdirectories
|
||||
for site in infra smarthome code retro; do
|
||||
if [ -d "dist/$site" ]; then
|
||||
echo " $site → dist/root/$site/"
|
||||
cp -r "dist/$site" "dist/root/$site"
|
||||
else
|
||||
echo " WARNING: dist/$site not found, skipping"
|
||||
fi
|
||||
done
|
||||
|
||||
# Copy CSS to root (accessible from all subdirectories)
|
||||
echo " Copying shared CSS to root..."
|
||||
cp shared/base.css dist/root/shared-base.css
|
||||
cp shared/favicon/hub.svg dist/root/favicon.svg
|
||||
|
||||
# Theme CSS files are already copied during build as theme.css
|
||||
# The root theme is hub's theme
|
||||
if [ -f "dist/root/theme.css" ]; then
|
||||
echo " Root theme.css already in place"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Merge complete. Final structure:"
|
||||
find dist/root -maxdepth 2 -type f | sort | head -50
|
||||
echo "..."
|
||||
echo ""
|
||||
echo "Total files:"
|
||||
find dist/root -type f | wc -l
|
||||
@@ -20,8 +20,8 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Lobster&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{{ site.pathPrefix | default('') }}/shared-base.css">
|
||||
<link rel="stylesheet" href="{{ site.pathPrefix | default('') }}/theme.css">
|
||||
<link rel="stylesheet" href="/shared-base.css">
|
||||
<style>:root { --accent: {% if section == 'infra' %}#99333A{% elif section == 'smarthome' %}#1f8a8a{% elif section == 'code' %}#3E5098{% elif section == 'retro' %}#8a6d3b{% else %}#3b6ea5{% endif %}; }</style>
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
{%- for page in collections.all %}
|
||||
{%- set sitemapPages = collections[sitemapCollection] if sitemapCollection else collections.all %}
|
||||
{%- for page in sitemapPages %}
|
||||
{%- if not page.url.includes("404") %}
|
||||
<url>
|
||||
<loc>{{ site.url }}{{ site.pathPrefix | default("") }}{{ page.url }}</loc>
|
||||
<loc>{{ site.url }}{{ pathPrefix }}{{ page.url }}</loc>
|
||||
<lastmod>{{ page.date | date }}</lastmod>
|
||||
<changefreq>{% if page.url == "/" %}weekly{% else %}monthly{% endif %}</changefreq>
|
||||
<priority>{% if page.url == "/" %}1.0{% elif parent %}0.8{% else %}0.7{% endif %}</priority>
|
||||
|
||||
+2
-1
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: "Page Not Found"
|
||||
section: "smarthome"
|
||||
tags: "smarthome"
|
||||
description: "The page you are looking for does not exist."
|
||||
layout: base.njk
|
||||
permalink: /404.html
|
||||
permalink: /smarthome/404.html
|
||||
---
|
||||
<div class="error-page">
|
||||
<h1>404</h1>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "Air Quality Dashboard"
|
||||
section: "smarthome"
|
||||
parent: "/"
|
||||
tags: "smarthome"
|
||||
parent: "/smarthome/"
|
||||
description: "Real-time temperature and humidity monitoring across all rooms, powered by InfluxDB and Chart.js."
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "AirPlay Audio"
|
||||
section: "smarthome"
|
||||
parent: "/"
|
||||
tags: "smarthome"
|
||||
parent: "/smarthome/"
|
||||
description: "Multi-room AirPlay receivers with software volume boost on Raspberry Pi devices."
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "Balkon Pi"
|
||||
section: "smarthome"
|
||||
parent: "/"
|
||||
tags: "smarthome"
|
||||
parent: "/smarthome/"
|
||||
description: "Balcony sensor, lighting, and audio automation running on a Raspberry Pi Zero W."
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
module.exports = function (eleventyConfig) {
|
||||
eleventyConfig.addGlobalData("site", { url: "https://www.moonweb.org", pathPrefix: "/smarthome" });
|
||||
eleventyConfig.addFilter("date", (d) => d.toISOString());
|
||||
eleventyConfig.addPassthroughCopy({ "shared/theme-smarthome.css": "theme.css" });
|
||||
eleventyConfig.addPassthroughCopy({ "shared/base.css": "shared-base.css" });
|
||||
eleventyConfig.addPassthroughCopy({ "shared/favicon/smarthome.svg": "favicon.svg" });
|
||||
eleventyConfig.addPassthroughCopy({ "smarthome/robots.txt": "robots.txt" });
|
||||
|
||||
return {
|
||||
dir: {
|
||||
input: "smarthome",
|
||||
includes: "../shared/_includes",
|
||||
output: "dist/smarthome"
|
||||
},
|
||||
serverOptions: {
|
||||
host: "0.0.0.0",
|
||||
port: 8083
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: "Home Control Buttons"
|
||||
section: "smarthome"
|
||||
tags: "smarthome"
|
||||
description: "Overview of the central buttons dashboard used to control lighting, smart plugs, music, network devices and monitoring shortcuts around the apartment."
|
||||
layout: base.njk
|
||||
parent: "/"
|
||||
parent: "/smarthome/"
|
||||
---
|
||||
<h1>Home Control Buttons</h1>
|
||||
<div class="detail-content">
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "HomematicIP + MQTT"
|
||||
section: "smarthome"
|
||||
parent: "/"
|
||||
tags: "smarthome"
|
||||
parent: "/smarthome/"
|
||||
description: "How HomematicIP room sensors are bridged into MQTT and stored in InfluxDB for dashboards."
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: "Smart Home Projects — Home Assistant, MQTT & IoT | moonweb"
|
||||
section: "smarthome"
|
||||
tags: "smarthome"
|
||||
description: "Smart home projects: Home Assistant, MQTT sensors, AirPlay audio, OctoPrint, and energy monitoring."
|
||||
layout: base.njk
|
||||
sections:
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "Kids RFID MP3 Player"
|
||||
section: "smarthome"
|
||||
parent: "/"
|
||||
tags: "smarthome"
|
||||
parent: "/smarthome/"
|
||||
description: "A custom Arduino-based RFID audio player for kids, inspired by the Hoerbert and Tonuino projects."
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "OctoPrint"
|
||||
section: "smarthome"
|
||||
parent: "/"
|
||||
tags: "smarthome"
|
||||
parent: "/smarthome/"
|
||||
description: "Remote 3D printer control and camera streaming with OctoPrint on Raspberry Pi."
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
permalink: /sitemap.xml
|
||||
permalink: /smarthome/sitemap.xml
|
||||
sitemapCollection: "smarthome"
|
||||
eleventyExcludeFromCollections: true
|
||||
---
|
||||
{% include "sitemap.njk" %}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "Tasmota & Energy Monitoring"
|
||||
section: "smarthome"
|
||||
parent: "/"
|
||||
tags: "smarthome"
|
||||
parent: "/smarthome/"
|
||||
description: "Smart plugs and power monitoring across the flat with daily usage statistics."
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "YouTube Archiving: TubeArchivist & TubeSync"
|
||||
section: "smarthome"
|
||||
parent: "/"
|
||||
tags: "smarthome"
|
||||
parent: "/smarthome/"
|
||||
description: "Self-hosted YouTube archiving and media management running on the Synology NAS, comparing TubeArchivist and TubeSync."
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user