mirror of
https://github.com/skoelle/moonweb-site.git
synced 2026-09-17 17:00:25 +00:00
Compare commits
31
Commits
a97c3d3a89
...
a3829f1c48
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3829f1c48 | ||
|
|
b202efaefc | ||
|
|
2e9f9a2395 | ||
|
|
8e54383b5a | ||
|
|
19f492b7e0 | ||
|
|
a5b4a20895 | ||
|
|
9ffbadb3db | ||
|
|
01097ff89a | ||
|
|
79a47ceeaa | ||
|
|
26ec966b51 | ||
|
|
09a170d5bf | ||
|
|
4ebd767cd1 | ||
|
|
9a1d1ebbfe | ||
|
|
7843d0061a | ||
|
|
ecbda171a6 | ||
|
|
5b70c7df2f | ||
|
|
177142fcf5 | ||
|
|
9c736532e1 | ||
|
|
401868cde5 | ||
|
|
dbb791b1a6 | ||
|
|
c77ad91e0e | ||
|
|
10b05736c8 | ||
|
|
84cd3f8562 | ||
|
|
9cda842250 | ||
|
|
37f7d8f1fb | ||
|
|
c76de3ec36 | ||
|
|
7524210768 | ||
|
|
41a9236ca7 | ||
|
|
7c3e29d282 | ||
|
|
2b997c11d0 | ||
|
|
466df3bd04 |
@@ -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
|
||||
@@ -0,0 +1,118 @@
|
||||
name: Build & Deploy moonweb.org
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- 'stefankoelle/**'
|
||||
- '.github/workflows/deploy-stefankoelle.yml'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- 'stefankoelle/**'
|
||||
- '.github/workflows/deploy-stefankoelle.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODE_VERSION: "22"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build moonweb sites
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: "npm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build all moonweb sites
|
||||
run: npm run build
|
||||
|
||||
- name: Build timecapsule
|
||||
run: npm run build:timecapsule
|
||||
|
||||
- name: Validate build output
|
||||
run: |
|
||||
echo "Build output:"
|
||||
find dist/moonweb -maxdepth 2 -type f | sort | head -40
|
||||
echo "..."
|
||||
echo "Total files:"
|
||||
find dist/moonweb -type f | wc -l
|
||||
|
||||
# Verify key directories exist
|
||||
for dir in "" infra smarthome code retro timecapsule; do
|
||||
if [ -n "$dir" ] && [ ! -d "dist/moonweb/$dir" ]; then
|
||||
echo "Error: dist/moonweb/$dir not found"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: dist-moonweb
|
||||
path: dist/moonweb
|
||||
retention-days: 7
|
||||
|
||||
deploy:
|
||||
name: Deploy to IONOS
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
steps:
|
||||
- name: Download build artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: dist-moonweb
|
||||
path: dist/moonweb
|
||||
|
||||
- name: Deploy via SFTP
|
||||
uses: wlixcc/SFTP-Deploy-Action@v1.2.6
|
||||
with:
|
||||
local_path: './dist/moonweb/*'
|
||||
server: ${{ secrets.IONOS_SFTP_HOST }}
|
||||
username: ${{ secrets.IONOS_SFTP_USER }}
|
||||
password: ${{ secrets.IONOS_SFTP_PASSWORD }}
|
||||
remote_path: '/websites/moonweb/'
|
||||
port: 22
|
||||
sftp_only: true
|
||||
|
||||
summary:
|
||||
name: Deploy Summary
|
||||
needs: deploy
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
steps:
|
||||
- name: Print deployment summary
|
||||
run: |
|
||||
echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "${{ needs.deploy.result }}" = "success" ]; then
|
||||
echo "moonweb.org deployed successfully!" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "Deployment failed. Check the deploy job for details." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Sites" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Site | URL |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|------|-----|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| hub | https://www.moonweb.org/ |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| infra | https://www.moonweb.org/infra/ |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| smarthome | https://www.moonweb.org/smarthome/ |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| code | https://www.moonweb.org/code/ |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| retro | https://www.moonweb.org/retro/ |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| timecapsule | https://www.moonweb.org/timecapsule/ |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Required repo secrets:
|
||||
# IONOS_SFTP_HOST — IONOS SFTP server hostname
|
||||
# IONOS_SFTP_USER — IONOS SFTP username
|
||||
# IONOS_SFTP_PASSWORD — IONOS SFTP password
|
||||
@@ -1,139 +0,0 @@
|
||||
name: Build & Deploy moonweb-site
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- 'stefankoelle/**'
|
||||
- '.github/workflows/deploy-stefankoelle.yml'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- 'stefankoelle/**'
|
||||
- '.github/workflows/deploy-stefankoelle.yml'
|
||||
|
||||
# Cancel in-progress runs for the same branch
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODE_VERSION: "22"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ matrix.site }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
site: [hub, infra, smarthome, code, retro]
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: "npm"
|
||||
|
||||
- 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
|
||||
|
||||
deploy:
|
||||
name: Deploy ${{ matrix.site }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- site: hub
|
||||
cf_project: moonweb-hub
|
||||
- site: infra
|
||||
cf_project: moonweb-infra
|
||||
- site: smarthome
|
||||
cf_project: moonweb-smarthome
|
||||
- site: code
|
||||
cf_project: moonweb-code
|
||||
- site: retro
|
||||
cf_project: moonweb-retro
|
||||
steps:
|
||||
- name: Download build artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: dist-${{ matrix.site }}
|
||||
path: dist/${{ matrix.site }}
|
||||
|
||||
- name: Create wrangler.toml for Worker
|
||||
run: |
|
||||
echo 'name = "${{ matrix.cf_project }}"' > wrangler.toml
|
||||
echo 'compatibility_date = "2024-01-01"' >> wrangler.toml
|
||||
echo '' >> wrangler.toml
|
||||
echo '[assets]' >> wrangler.toml
|
||||
echo 'directory = "./dist/${{ matrix.site }}"' >> wrangler.toml
|
||||
echo 'not_found_handling = "404-page"' >> wrangler.toml
|
||||
|
||||
- name: Deploy to Cloudflare Workers
|
||||
uses: cloudflare/wrangler-action@v4
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: deploy
|
||||
|
||||
summary:
|
||||
name: Deploy Summary
|
||||
needs: deploy
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
steps:
|
||||
- name: Print deployment summary
|
||||
run: |
|
||||
echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "${{ needs.deploy.result }}" = "success" ]; then
|
||||
echo "✅ All sites deployed successfully!" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ Deployment failed. Check the deploy jobs for details." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Sites" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Site | URL |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|------|-----|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| hub | https://hub.moonweb.org |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| infra | https://infra.moonweb.org |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| smarthome | https://smarthome.moonweb.org |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| code | https://code.moonweb.org |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| retro | https://retro.moonweb.org |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Required repo secrets:
|
||||
# CLOUDFLARE_API_TOKEN — Cloudflare API token with Workers:Edit permission
|
||||
# CLOUDFLARE_ACCOUNT_ID — Cloudflare account ID
|
||||
#
|
||||
# Required one-time setup (see PLAN.md Phase 2):
|
||||
# Create Cloudflare Workers and bind custom domains.
|
||||
# DNS is already on Cloudflare (SPEC.md §4.6).
|
||||
#
|
||||
@@ -0,0 +1,58 @@
|
||||
name: Setup Cloudflare Redirects
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
action:
|
||||
description: "Action to perform"
|
||||
required: true
|
||||
default: "apply"
|
||||
type: choice
|
||||
options:
|
||||
- apply
|
||||
- status
|
||||
- dry-run
|
||||
- delete
|
||||
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
|
||||
|
||||
jobs:
|
||||
redirects:
|
||||
name: Cloudflare Redirects
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Validate secrets
|
||||
run: |
|
||||
if [ -z "$CLOUDFLARE_API_TOKEN" ]; then
|
||||
echo "Error: CLOUDFLARE_API_TOKEN secret is not set"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$CLOUDFLARE_ZONE_ID" ]; then
|
||||
echo "Error: CLOUDFLARE_ZONE_ID secret is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Run ${{ inputs.action }}
|
||||
run: |
|
||||
case "${{ inputs.action }}" in
|
||||
status)
|
||||
./scripts/cloudflare/setup-redirects.sh --status
|
||||
;;
|
||||
dry-run)
|
||||
./scripts/cloudflare/setup-redirects.sh --dry-run
|
||||
;;
|
||||
delete)
|
||||
./scripts/cloudflare/setup-redirects.sh --delete
|
||||
;;
|
||||
*)
|
||||
./scripts/cloudflare/setup-redirects.sh
|
||||
;;
|
||||
esac
|
||||
|
||||
# Required repo secrets:
|
||||
# CLOUDFLARE_API_TOKEN — API token with Dynamic Redirects Write permission
|
||||
# CLOUDFLARE_ZONE_ID — Zone ID for moonweb.org (found in Cloudflare dashboard)
|
||||
@@ -15,3 +15,6 @@ dist/
|
||||
.DS_Store
|
||||
*.swp
|
||||
*.swo
|
||||
timecapsule/_site/
|
||||
timecapsule/node_modules/
|
||||
.tmp/
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
# AGENTS.md — moonweb-site
|
||||
|
||||
## Projektübersicht
|
||||
## Projektuebersicht
|
||||
|
||||
Monorepo für 5 statische Websites unter moonweb.org + stefankoelle.de, basierend auf Eleventy (11ty).
|
||||
Monorepo fuer 6 statische Websites unter www.moonweb.org + stefankoelle.de, basierend auf Eleventy (11ty).
|
||||
|
||||
### Websites (Cloudflare Pages)
|
||||
### Websites (IONOS SFTP)
|
||||
|
||||
| Site | Domain | Zweck | Status |
|
||||
|------|--------|-------|--------|
|
||||
| hub | hub.moonweb.org | Zentrale Indexseite, verlinkt alles | Fertig |
|
||||
| infra | infra.moonweb.org | Infrastruktur-Übersicht (Proxmox, Synology, Netzwerk) | Übersicht + 3 Detailseiten |
|
||||
| smarthome | smarthome.moonweb.org | Smart Home Projekte und Dashboards | Übersicht + 6 Detailseiten |
|
||||
| code | code.moonweb.org | GitHub-Projekte (aggregiert via .moonweb.yml) | Fertig (14 Repos) |
|
||||
| retro | retro.moonweb.org | Physische Retro-Hardware | Nur Übersicht (WIP) |
|
||||
| Site | URL | Zweck | Status |
|
||||
|------|-----|-------|--------|
|
||||
| hub | www.moonweb.org/ | Zentrale Indexseite, verlinkt alles | Fertig |
|
||||
| infra | www.moonweb.org/infra/ | Infrastruktur-Uebersicht (Proxmox, Synology, Netzwerk) | Uebersicht + 8 Detailseiten |
|
||||
| smarthome | www.moonweb.org/smarthome/ | Smart Home Projekte und Dashboards | Uebersicht + 9 Detailseiten |
|
||||
| code | www.moonweb.org/code/ | GitHub-Projekte (aggregiert via .moonweb.yml) | Fertig |
|
||||
| retro | www.moonweb.org/retro/ | Physische Retro-Hardware | Uebersicht + 13 Detailseiten |
|
||||
| timecapsule | www.moonweb.org/timecapsule/ | 2000er Internet-Zeitkapsel (altes Design) | Fertig |
|
||||
|
||||
### Externe Sites (SFTP-Deployment)
|
||||
|
||||
@@ -22,19 +23,22 @@ Monorepo für 5 statische Websites unter moonweb.org + stefankoelle.de, basieren
|
||||
|
||||
### Andere Sites (nicht im Monorepo)
|
||||
|
||||
- www.moonweb.org — 2000er Internet-Zeitkapsel
|
||||
- 28k8.moonweb.org — 90er BBS/Scene-Archiv
|
||||
- buildbroken.moonweb.org — .NET Open Space Blog Archiv
|
||||
|
||||
## Dateistruktur
|
||||
|
||||
```
|
||||
moonweb-site/
|
||||
├── hub/ # Eleventy-Config + index.njk
|
||||
├── infra/ # Eleventy-Config + index.njk + 3 Subseiten
|
||||
├── smarthome/ # Eleventy-Config + index.njk + 6 Subseiten
|
||||
├── code/ # Eleventy-Config + index.njk
|
||||
│ └── _data/repos.json
|
||||
├── retro/ # Eleventy-Config + index.njk
|
||||
├── hub/ # Index + Redirects (.htm) + impressum.njk
|
||||
├── infra/ # Index.njk + 8 Subseiten
|
||||
├── smarthome/ # Index.njk + 9 Subseiten
|
||||
├── code/ # Index.njk
|
||||
├── retro/ # Index.njk + 13 Subseiten
|
||||
├── timecapsule/ # Eleventy 2.x (eigene Config)
|
||||
│ ├── eleventy.config.js
|
||||
│ ├── package.json
|
||||
│ └── src/ # 2001er Retro-Content
|
||||
├── stefankoelle/ # Eleventy-Config + Onepager
|
||||
│ ├── eleventy.config.js
|
||||
│ ├── _includes/
|
||||
@@ -48,14 +52,20 @@ moonweb-site/
|
||||
├── shared/ # Gemeinsame Komponenten
|
||||
│ ├── _includes/
|
||||
│ │ ├── base.njk # Basis-Layout (Header, Site-Switcher, Footer)
|
||||
│ │ └── card-grid.njk # Card-Grid Template
|
||||
│ ├── base.css
|
||||
│ └── theme-*.css
|
||||
│ │ ├── card-grid.njk # Card-Grid Template
|
||||
│ │ └── sitemap.njk # Zentrale Sitemap
|
||||
│ ├── base.css # Shared CSS (Layout, Cards, Typografie)
|
||||
│ └── favicon/ # Favicon-SVGs pro Section
|
||||
├── _data/
|
||||
│ └── repos.json # GitHub-Aggregator Output
|
||||
├── scripts/
|
||||
│ └── github-aggregator/
|
||||
│ ├── github-aggregator/ # Python: liest .moonweb.yml -> repos.json
|
||||
│ └── cloudflare/ # Redirect-Setup fuer alte Subdomains
|
||||
├── .github/workflows/
|
||||
│ ├── build-deploy.yml # CI/CD: Cloudflare Pages (5 Sites)
|
||||
│ └── deploy-stefankoelle.yml # CI/CD: IONOS SFTP (stefankoelle.de)
|
||||
│ ├── build-deploy-moonweb.yml # CI/CD: IONOS SFTP (www.moonweb.org)
|
||||
│ └── deploy-stefankoelle.yml # CI/CD: IONOS SFTP (stefankoelle.de)
|
||||
├── eleventy.config.js # Zentrale Eleventy-Config (alle moonweb Sites)
|
||||
├── .eleventyignore # Schliesst stefankoelle/, timecapsule/ aus
|
||||
├── DESIGN.md
|
||||
├── SPEC.md
|
||||
├── PLAN.md
|
||||
@@ -66,10 +76,11 @@ moonweb-site/
|
||||
|
||||
## Technischer Stack
|
||||
|
||||
- **SSG:** Eleventy (11ty) v3.1.6
|
||||
- **SSG:** Eleventy (11ty) v3.1.6 (hub, infra, smarthome, code, retro) - zentrale Config
|
||||
- **SSG:** Eleventy v2.0.1 (timecapsule - retro 2001 Design)
|
||||
- **Templates:** Nunjucks (.njk)
|
||||
- **CSS:** Variables-basiert mit Accent-Farben pro Site
|
||||
- **Deploy (moonweb):** Cloudflare Pages (5 separate Projects)
|
||||
- **CSS:** Variables-basiert mit Accent-Farben (inlined in base.njk)
|
||||
- **Deploy (moonweb):** IONOS SFTP
|
||||
- **Deploy (stefankoelle):** IONOS SFTP
|
||||
- **CI/CD:** GitHub Actions (2 Workflows)
|
||||
|
||||
@@ -77,32 +88,51 @@ moonweb-site/
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run prebuild # Pre-Build Tasks (CV PDF generieren)
|
||||
npm run dev:hub # localhost:8081
|
||||
npm run dev:infra # localhost:8082
|
||||
npm run dev:smarthome # localhost:8083
|
||||
npm run dev:code # localhost:8084
|
||||
npm run dev:retro # localhost:8085
|
||||
npm run dev:stefankoelle # localhost:8086
|
||||
npm run build # Alle Sites bauen
|
||||
cd timecapsule && npm install # Timecapsule Dependencies
|
||||
npm run prebuild:cv # Pre-Build Tasks (CV PDF generieren)
|
||||
npm run dev # Moonweb Sites (localhost:8081)
|
||||
npm run dev:stefankoelle # stefankoelle.de (localhost:8086)
|
||||
npm run dev:timecapsule # Timecapsule (localhost:8087)
|
||||
npm run build # Alle moonweb Sites
|
||||
npm run build:stefankoelle # Nur stefankoelle
|
||||
npm run build:timecapsule # Nur timecapsule
|
||||
npm run build:moonweb # Moonweb + Timecapsule (fuer Deployment)
|
||||
```
|
||||
|
||||
## Design-Prinzipien
|
||||
|
||||
1. **Header konsistent** — Identischer Site-Switcher auf allen Home-Sites
|
||||
2. **Content flexibel** — Detailseiten dürfen eigenes Layout haben
|
||||
3. **Accent-Farben:** hub=#3b6ea5, infra=#99333A, smarthome=#1f8a8a, code=#3E5098, retro=#8a6d3b
|
||||
2. **Content flexibel** — Detailseiten duerfen eigenes Layout haben
|
||||
3. **Accent-Farben:** hub=#3b6ea5, infra=#99333A, smarthome=#1f8a8a, code=#3E5098, retro=#8a6d3b (inlined in base.njk)
|
||||
4. **Englisch** — Alle Sites komplett auf Englisch
|
||||
5. **Keine Analytics** — Keine Tracking-Tools
|
||||
6. **Sensible Daten** — Infra-Content wird manuell redigiert (keine IPs, Keys, Passwörter)
|
||||
6. **Sensible Daten** — Infra-Content wird manuell redigiert (keine IPs, Keys, Passwoerter)
|
||||
|
||||
## URL-Struktur
|
||||
|
||||
Alle Sites sind unter `www.moonweb.org` als Subverzeichnisse erreichbar:
|
||||
- `www.moonweb.org/` — Hub (Root)
|
||||
- `www.moonweb.org/infra/` — Infra
|
||||
- `www.moonweb.org/smarthome/` — Smarthome
|
||||
- `www.moonweb.org/code/` — Code
|
||||
- `www.moonweb.org/retro/` — Retro
|
||||
- `www.moonweb.org/timecapsule/` — Timecapsule (2001 Design)
|
||||
- `www.moonweb.org/impressum/` — Impressum
|
||||
|
||||
Cloudflare Redirects leiten alte Subdomains weiter:
|
||||
- `hub.moonweb.org/*` → `www.moonweb.org/*`
|
||||
- `infra.moonweb.org/*` → `www.moonweb.org/infra/*`
|
||||
- `smarthome.moonweb.org/*` → `www.moonweb.org/smarthome/*`
|
||||
- `code.moonweb.org/*` → `www.moonweb.org/code/*`
|
||||
- `retro.moonweb.org/*` → `www.moonweb.org/retro/*`
|
||||
|
||||
## stefankoelle.de
|
||||
|
||||
- Eigene Eleventy-Config (nicht shared base.njk)
|
||||
- Eigenes CSS-Design (nicht moonweb design system)
|
||||
- Onepager mit Anchor-Links (bleibt so)
|
||||
- Deploy via SFTP auf IONOS `/deploy/stefankoelle/`
|
||||
- CV-Partial zentral pflegbar (einmal aendern → Web + PDF aktualisieren)
|
||||
- Deploy via SFTP auf IONOS `/websites/stefankoelle/`
|
||||
- CV-Partial zentral pflegbar (einmal aendern -> Web + PDF aktualisieren)
|
||||
- LED Matrix als eigene Seite unter stefankoelle.de/ledmatrix/
|
||||
|
||||
### CV PDF Generierung
|
||||
@@ -111,12 +141,12 @@ Das CV-PDF wird via WeasyPrint generiert:
|
||||
|
||||
```bash
|
||||
npm run pdf:cv # Einzelnes PDF generieren
|
||||
npm run prebuild # Alle Pre-Build Tasks (inkl. CV PDF)
|
||||
npm run prebuild:cv # Alle Pre-Build Tasks (inkl. CV PDF)
|
||||
```
|
||||
|
||||
Dateien:
|
||||
- `stefankoelle/pdf/cv-style.css` — WeasyPrint-Stylesheet (A4, Typografie)
|
||||
- `stefankoelle/pdf/build-pdf.sh` — Shell-Skript für PDF-Generierung
|
||||
- `stefankoelle/pdf/build-pdf.sh` — Shell-Skript fuer PDF-Generierung
|
||||
- `stefankoelle/cv-print.njk` — Standalone HTML-Template (nur CV-Content)
|
||||
- `stefankoelle/pdf/cv.pdf` — Generiertes PDF (Output)
|
||||
|
||||
@@ -124,21 +154,22 @@ Wichtig: Das PDF wird via Eleventy-Passthrough ins Build-Output kopiert (`dist/s
|
||||
|
||||
### CSS Cache-Busting
|
||||
|
||||
CSS-Dateien werden als Passthrough kopiert (kein Hash im Dateinamen). Bei CSS-Änderungen muss der Query-String in der `?v=N` Inkludierung erhöht werden:
|
||||
CSS-Dateien werden als Passthrough kopiert (kein Hash im Dateinamen). Bei CSS-Aenderungen muss der Query-String in der `?v=N` Inkludierung erhoeht werden:
|
||||
|
||||
- `stefankoelle/index.njk`: `<link rel="stylesheet" href="/assets/style.css?v=N">`
|
||||
- `stefankoelle/ledmatrix/index.njk`: `<link rel="stylesheet" href="assets/style.css?v=N">`
|
||||
|
||||
Bei jeder CSS-Anpassung `?v=N` um 1 erhöhen, sonst cached der Browser die alte Datei.
|
||||
Bei jeder CSS-Anpassung `?v=N` um 1 erhoehen, sonst cached der Browser die alte Datei.
|
||||
|
||||
## CI/CD
|
||||
|
||||
### Cloudflare Pages (moonweb)
|
||||
`.github/workflows/build-deploy.yml` baut hub, infra, smarthome, code, retro.
|
||||
### IONOS SFTP (www.moonweb.org)
|
||||
`.github/workflows/build-deploy-moonweb.yml` baut alle moonweb Sites (hub, infra, smarthome, code, retro, timecapsule) und deployed per SFTP.
|
||||
|
||||
Benötigte Secrets:
|
||||
- `CLOUDFLARE_API_TOKEN`
|
||||
- `CLOUDFLARE_ACCOUNT_ID`
|
||||
- `IONOS_SFTP_HOST`
|
||||
- `IONOS_SFTP_USER`
|
||||
- `IONOS_SFTP_PASSWORD`
|
||||
|
||||
### IONOS SFTP (stefankoelle.de)
|
||||
`.github/workflows/deploy-stefankoelle.yml` baut stefankoelle.de und deployed per SFTP.
|
||||
@@ -150,13 +181,15 @@ Benötigte Secrets:
|
||||
|
||||
## Offene Punkte
|
||||
|
||||
- retro/ ist bewusst rudimentär gehalten
|
||||
- retro/ ist bewusst rudimentaer gehalten
|
||||
- Querverlinkungen stefankoelle.de <-> smarthome (zukuenftig)
|
||||
- Ledmatrix ggf. nach smarthome verschieben (when ready)
|
||||
- Cloudflare Redirects einrichten (nach Deploy)
|
||||
- Google Search Console: Neue Property www.moonweb.org
|
||||
|
||||
## GitHub Aggregator
|
||||
|
||||
Das Skript `scripts/github-aggregator/aggregate.py` liest aus jedem public Repo unter `skoelle` die `.moonweb.yml` und generiert `code/_data/repos.json`.
|
||||
Das Skript `scripts/github-aggregator/aggregate.py` liest aus jedem public Repo unter `skoelle` die `.moonweb.yml` und generiert `_data/repos.json`.
|
||||
|
||||
### .moonweb.yml Format
|
||||
|
||||
@@ -191,10 +224,10 @@ python3 -m venv .venv
|
||||
|
||||
## Text / Stil
|
||||
|
||||
- Keine Em-Dashes (—) verwenden, stattdessen umformulieren (Komma, Satzzeichen, neu formulieren)
|
||||
- Keine Em-Dashes verwenden, stattdessen umformulieren (Komma, Satzzeichen, neu formulieren)
|
||||
|
||||
## Python / pip
|
||||
|
||||
- Niemals `pip install` direkt ausführen
|
||||
- Niemals `pip install` direkt ausfuehren
|
||||
- Immer ein virtuelles Umfeld (`.venv`) anlegen und darin arbeiten
|
||||
- `python3 -m venv .venv` im Projektverzeichnis
|
||||
|
||||
@@ -1,64 +1,74 @@
|
||||
# moonweb.org — Implementation Plan
|
||||
|
||||
Companion to `SPEC.md`. Defines order of work. All sites (hub, infra, smarthome, code, retro, stefankoelle) launch **simultaneously** — no staged rollout by domain.
|
||||
Companion to `SPEC.md`. Documents the completed migration from separate subdomain sites to a unified `www.moonweb.org` subdirectory structure.
|
||||
|
||||
## Phase 0 — Repo & tooling setup
|
||||
|
||||
1. Create the `moonweb-site` monorepo (private until first launch, then can stay private since Pages deploys the built output, not the source).
|
||||
2. Initialize Eleventy project structure per `SPEC.md §10`.
|
||||
3. Set up `shared/layout-base.njk` (header + card-grid) and one `theme-*.css` per accent color (hub, infra, smarthome, code, retro).
|
||||
4. Verify local dev server works per site (`npm run dev:<site>`) before any content work starts.
|
||||
1. Created the `moonweb-site` monorepo.
|
||||
2. Initialized Eleventy project structure per `SPEC.md §10`.
|
||||
3. Set up `shared/_includes/base.njk` (header + card-grid) with accent colors inlined.
|
||||
4. Verified local dev server works per site.
|
||||
|
||||
## Phase 1 — Content migration & authoring (per domain)
|
||||
|
||||
Work order within this phase is flexible since all domains launch together; suggested sequence based on how self-contained each domain's content is:
|
||||
|
||||
1. **code** — simplest case, no detail pages, no sensitive-data filtering needed.
|
||||
- Add `.moonweb.yml` to each GitHub repo (see `SPEC.md §8`).
|
||||
- Build the aggregator script, run it once, generate `code/_data/repos.json`.
|
||||
- Build the overview page grouped by subcategory (Automation & Sync, Dev-Tools, Web Apps, Firmware/Hardware, Misc).
|
||||
2. **smarthome** — overview first, detail pages only where content already exists.
|
||||
- Draft the overview: how the smart home is structured (buttons/control, sensors, calendar, weather, media).
|
||||
- For each existing topic with enough material (HomematicIP/MQTT, Tasmota, LED-Matrix *reference only, no migration*, M5Stack/WT32SC01 dashboards, WetterAPI, AirPlay, OctoPi, TubeArchivist), decide case by case: enough content → detail page; too thin → mention in overview only, no placeholder.
|
||||
- Translate source material to English during authoring (one-time AI pass per document, per `SPEC.md §7`).
|
||||
3. **infra** — overview only, redaction pass required.
|
||||
- Draft a shallow, structured overview: Proxmox/pve2, Synology, VLANs, Fritz!Box mesh, SSO, Docker hosting model, plus the "how I work" section (OpenClaw/OpenCode setup, dev workflow).
|
||||
- Apply the redaction rule from `SPEC.md §6` while translating — strip IPs, keys, credentials, internal hostnames from every source document before it becomes public content.
|
||||
- Skip detail pages unless a topic can be described without any sensitive detail.
|
||||
4. **retro** — minimal effort, rudimentary only.
|
||||
- One overview page listing what hardware exists.
|
||||
- Add "work in progress" notices for anything that isn't ready — better an honest short page than none.
|
||||
5. **hub** — build last within this phase since it links to all the others.
|
||||
- One-line description + link per destination (infra, smarthome, code, retro, stefankoelle, and external links to www.moonweb.org, 28k8.moonweb.org).
|
||||
1. **code** — GitHub aggregator, `_data/repos.json`, overview page grouped by subcategory.
|
||||
2. **smarthome** — overview + detail pages where content exists.
|
||||
3. **infra** — shallow overview with redaction pass (no IPs, keys, passwords).
|
||||
4. **retro** — minimal overview with WIP notices.
|
||||
5. **hub** — central index linking all sites, including redirect `.htm` files for old www.moonweb.org paths.
|
||||
6. **timecapsule** — integrated from moonweb-www, preserves original 2001 design under `/timecapsule/`.
|
||||
|
||||
## Phase 2 — CI/CD
|
||||
|
||||
1. Write `.github/workflows/build-deploy.yml`: builds all five sites in one job (or matrix), then deploys each output folder to its corresponding Cloudflare Pages project via `wrangler pages deploy`.
|
||||
2. Create five Cloudflare Pages projects (hub, infra, smarthome, code, retro), each bound to its target custom domain (DNS already on Cloudflare, no extra setup needed).
|
||||
3. Do one full dry run per site locally before the first real deploy.
|
||||
1. GitHub Actions workflow `build-deploy-moonweb.yml`: single Eleventy build for all moonweb sites + timecapsule build, deploy via IONOS SFTP.
|
||||
2. GitHub Actions workflow `deploy-stefankoelle.yml`: stefankoelle.de build, deploy via IONOS SFTP.
|
||||
3. Cloudflare redirect rules for old subdomains (`scripts/cloudflare/`).
|
||||
|
||||
## Phase 3 — Launch
|
||||
## Phase 3 — Consolidation to www.moonweb.org
|
||||
|
||||
1. Deploy all five sites simultaneously.
|
||||
2. Point the respective custom domains at their Pages projects.
|
||||
3. Apex domain `moonweb.org` keeps redirecting to `www.moonweb.org` for now (no change in this launch).
|
||||
4. Smoke-test cross-linking: hub → each site, site-switcher on infra/smarthome/code/retro, smarthome → external ledmatrix link, code cards → GitHub links.
|
||||
All moonweb.org sites now live under `www.moonweb.org` as subdirectories:
|
||||
|
||||
## Phase 4 — Explicitly deferred (not part of this build)
|
||||
| URL | Content |
|
||||
|-----|---------|
|
||||
| `www.moonweb.org/` | Hub (root) |
|
||||
| `www.moonweb.org/infra/` | Infrastructure |
|
||||
| `www.moonweb.org/smarthome/` | Smart Home |
|
||||
| `www.moonweb.org/code/` | Code catalog |
|
||||
| `www.moonweb.org/retro/` | Retro hardware |
|
||||
| `www.moonweb.org/timecapsule/` | 2000s time capsule |
|
||||
| `www.moonweb.org/impressum/` | Legal notice |
|
||||
|
||||
- Deciding on 28k8.moonweb.org's future (stay separate vs. eventual monorepo inclusion).
|
||||
- Building any Perplexity-backchannel mechanism for reusing published site content in this project.
|
||||
- Redirecting the apex domain from `www.moonweb.org` to `hub.moonweb.org`.
|
||||
- Any analytics, "last updated" timestamps, or scheduled/automated content generation beyond the one-time GitHub aggregator run.
|
||||
Cloudflare redirects forward old subdomains:
|
||||
- `hub.moonweb.org/*` → `www.moonweb.org/*`
|
||||
- `infra.moonweb.org/*` → `www.moonweb.org/infra/*`
|
||||
- `smarthome.moonweb.org/*` → `www.moonweb.org/smarthome/*`
|
||||
- `code.moonweb.org/*` → `www.moonweb.org/code/*`
|
||||
- `retro.moonweb.org/*` → `www.moonweb.org/retro/*`
|
||||
|
||||
## Definition of done for this build
|
||||
### Technical consolidation
|
||||
|
||||
- All sites live on their respective platforms:
|
||||
- hub, infra, smarthome, code, retro on Cloudflare Pages under their intended domains.
|
||||
- stefankoelle.de on IONOS via SFTP deployment.
|
||||
- code.moonweb.org reflects the current GitHub repos via the `.moonweb.yml` aggregator, grouped by subcategory.
|
||||
- smarthome.moonweb.org gives an accurate picture of what's running on the homelab today, with detail pages only where content already existed.
|
||||
- infra.moonweb.org describes the stack shallowly with zero sensitive data leaked.
|
||||
- retro.moonweb.org exists with an honest, minimal overview (WIP notices allowed).
|
||||
- hub.moonweb.org correctly links everything, including the external sites (www.moonweb.org, 28k8.moonweb.org).
|
||||
- stefankoelle.de is a working onepager with CV, Projects, Languages, Impressum, and the LED Matrix documentation sub-page.
|
||||
- Single `eleventy.config.js` at root with computed `pathPrefix` per section.
|
||||
- Shared `base.njk` layout with inlined accent colors (no per-site theme CSS).
|
||||
- Central `_data/repos.json` (moved from `code/_data/`).
|
||||
- Central sitemap (`shared/_includes/sitemap.njk`) with all 36 pages.
|
||||
- Single `robots.txt` at root.
|
||||
- `.eleventyignore` excludes `stefankoelle/` and `timecapsule/` (built separately).
|
||||
- `build-pdf.sh` temporarily renames `.eleventyignore` for stefankoelle build.
|
||||
|
||||
## Definition of done — Achieved
|
||||
|
||||
- All sites live under `www.moonweb.org` as subdirectories, deployed via IONOS SFTP.
|
||||
- `www.moonweb.org/code/` reflects the current GitHub repos via the `.moonweb.yml` aggregator.
|
||||
- `www.moonweb.org/smarthome/` gives an accurate picture of what's running on the homelab.
|
||||
- `www.moonweb.org/infra/` describes the stack shallowly with zero sensitive data leaked.
|
||||
- `www.moonweb.org/retro/` exists with an honest, minimal overview.
|
||||
- `www.moonweb.org/` correctly links everything.
|
||||
- `stefankoelle.de` is a working onepager with CV, Projects, Languages, Impressum, and LED Matrix documentation.
|
||||
- Old subdomains redirect via Cloudflare to new paths.
|
||||
- Old www.moonweb.org content preserved under `/timecapsule/`.
|
||||
|
||||
## Remaining items
|
||||
|
||||
- Cloudflare redirect rules need activation (script ready, needs `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ZONE_ID` secrets, or manual dashboard setup).
|
||||
- Google Search Console: new property `www.moonweb.org` not yet created.
|
||||
- `moonweb-www` repository not yet archived.
|
||||
|
||||
@@ -1,182 +1,157 @@
|
||||
# 🌙 moonweb-site
|
||||
# moonweb-site
|
||||
|
||||
Monorepo for the **moonweb.org** homelab — five static sites built with [Eleventy](https://www.11ty.dev/), deployed to [Cloudflare Pages](https://pages.cloudflare.com/).
|
||||
Monorepo for the **moonweb.org** homelab — six static sites built with [Eleventy](https://www.11ty.dev/), deployed to [IONOS SFTP](https://www.ionos.de/).
|
||||
|
||||
```
|
||||
hub.moonweb.org → 🏠 Central index & gateway
|
||||
infra.moonweb.org → 🏗️ Infrastructure overview (Proxmox, Synology, Docker)
|
||||
smarthome.moonweb.org → 🏡 Smart home projects & dashboards
|
||||
code.moonweb.org → 💻 Curated GitHub project catalog
|
||||
retro.moonweb.org → 🕹️ Physical retro hardware collection
|
||||
stefankoelle.de → 👤 CV, career, personal site (LED Matrix docs)
|
||||
www.moonweb.org/ -> Central index & gateway
|
||||
www.moonweb.org/infra/ -> Infrastructure overview (Proxmox, Synology, Docker)
|
||||
www.moonweb.org/smarthome/ -> Smart home projects & dashboards
|
||||
www.moonweb.org/code/ -> Curated GitHub project catalog
|
||||
www.moonweb.org/retro/ -> Physical retro hardware collection
|
||||
www.moonweb.org/timecapsule/ -> 2000s internet time capsule (retro design)
|
||||
stefankoelle.de -> CV, career, personal site (LED Matrix docs)
|
||||
```
|
||||
|
||||
> **Other sites** (not in this monorepo): [www.moonweb.org](https://www.moonweb.org) (2000s time capsule), [28k8.moonweb.org](https://28k8.moonweb.org) (90s BBS archive).
|
||||
> **Other sites** (not in this monorepo): [28k8.moonweb.org](https://28k8.moonweb.org) (90s BBS archive), [buildbroken.moonweb.org](https://buildbroken.moonweb.org) (.NET Open Space blog).
|
||||
|
||||
---
|
||||
|
||||
## 📐 Architecture
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ GitHub Actions CI │
|
||||
│ ┌───────────┐ ┌────────────┐ ┌─────────────┐ ┌─────────┐ │
|
||||
│ │ build:hub │ │build:infra │ │ build:smart │ │ build:… │ │
|
||||
│ └─────┬─────┘ └─────┬──────┘ └──────┬──────┘ └────┬────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ▼ ▼ ▼ ▼ │
|
||||
│ dist/hub/ dist/infra/ dist/smarthome/ dist/…/ │
|
||||
└────────┬───────────────────────────────────────────┬────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────┐ ┌─────────────────┐
|
||||
│ Cloudflare Pages │ │ IONOS SFTP │
|
||||
│ (5 sites) │ │ (stefankoelle) │
|
||||
└────────┬─────────┘ └────────┬────────┘
|
||||
▼ ▼
|
||||
hub / infra / ... stefankoelle.de
|
||||
+-------------------------------------------------------------------+
|
||||
| GitHub Actions CI |
|
||||
| +-------------------+ +---------------------+ |
|
||||
| | build-deploy- | | deploy-stefankoelle | |
|
||||
| | moonweb | | | |
|
||||
| +--------+----------+ +----------+----------+ |
|
||||
| | | |
|
||||
| v v |
|
||||
| npm run build npm run build:stefankoelle |
|
||||
| + build:timecapsule | |
|
||||
| | | |
|
||||
| v v |
|
||||
| dist/moonweb/ dist/stefankoelle/ |
|
||||
+-----------+------------------------+-------------------------------+
|
||||
| |
|
||||
v v
|
||||
+------------------+ +------------------+
|
||||
| IONOS SFTP | | IONOS SFTP |
|
||||
| (www.moonweb.org)| | (stefankoelle.de)|
|
||||
+--------+---------+ +--------+---------+
|
||||
v v
|
||||
www.moonweb.org/* stefankoelle.de
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Tech Stack
|
||||
## Tech Stack
|
||||
|
||||
| Layer | Technology | Why |
|
||||
|-------|-----------|-----|
|
||||
| **SSG** | [Eleventy 3.1.6](https://www.11ty.dev/) | Markdown/YAML-first, minimal JS, `_data` folders map directly to aggregator output, low maintenance for 5 sites |
|
||||
| **SSG** | [Eleventy 3.1.6](https://www.11ty.dev/) | Markdown/YAML-first, minimal JS, `_data` folders map directly to aggregator output |
|
||||
| **SSG (timecapsule)** | [Eleventy 2.0.1](https://www.11ty.dev/) | Legacy 2001 design, CommonJS config |
|
||||
| **Templates** | [Nunjucks](https://mozilla.github.io/nunjucks/) | Shared `base.njk` layout with site-switcher header, `card-grid.njk` for index pages |
|
||||
| **Styling** | Custom CSS (variables-based) | `base.css` for shared layout, `theme-*.css` per domain accent color, no build step needed |
|
||||
| **Styling** | Custom CSS (variables-based) | `base.css` for shared layout, accent colors inlined in `base.njk` |
|
||||
| **Fonts** | [Lobster](https://fonts.google.com/specimen/Lobster) (Google Fonts) | Distinctive heading font across all sites |
|
||||
| **CI/CD** | [GitHub Actions](https://github.com/features/actions) | Matrix build for all 5 sites, artifact upload, parallel deploy |
|
||||
| **Deploy** | [Cloudflare Workers](https://workers.cloudflare.com/) | Static asset hosting via `wrangler pages deploy`, one worker per site |
|
||||
| **DNS** | Cloudflare | Already managing DNS — zero additional setup for Pages custom domains |
|
||||
| **CI/CD** | [GitHub Actions](https://github.com/features/actions) | Single build job, parallel SFTP deploy |
|
||||
| **Deploy** | IONOS SFTP | Static hosting via SFTP upload |
|
||||
| **DNS** | Cloudflare | DNS management + redirects from old subdomains |
|
||||
| **GitHub Catalog** | Python aggregator | Reads `.moonweb.yml` from each repo, outputs `repos.json` |
|
||||
| **Runtime** | Fully static | No server-side code, no containers, no database — pure HTML/CSS/JS |
|
||||
| **Runtime** | Fully static | No server-side code, no containers, no database |
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Design System
|
||||
|
||||
### Header-consistent, content-flexible
|
||||
|
||||
- **Header is identical** across all home-section sites: site-switcher (hub · infra · smarthome · code · retro · cv), domain accent color, Lobster title font.
|
||||
- **Index pages** use a shared card-grid layout with grouped sections.
|
||||
- **Detail pages** keep the same header but use a freer layout below it (e.g., pin tables, API docs, photos in free arrangement).
|
||||
|
||||
### Accent colors
|
||||
|
||||
| Domain | Color | Hex |
|
||||
|--------|-------|-----|
|
||||
| hub | Neutral blue | `#3b6ea5` |
|
||||
| infra | Grey-blue | `#99333A` |
|
||||
| smarthome | Teal | `#1f8a8a` |
|
||||
| code | Violet | `#3E5098` |
|
||||
| retro | Warm brown | `#8a6d3b` |
|
||||
|
||||
### Emojis
|
||||
|
||||
Each card on index pages has an emoji for visual navigation — consistent across hub, infra, smarthome, code, and retro.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Local Development
|
||||
## Local Development
|
||||
|
||||
```bash
|
||||
npm install # install Eleventy + deps
|
||||
cd timecapsule && npm install # timecapsule has own deps (Eleventy 2.x)
|
||||
|
||||
npm run dev:hub # http://localhost:8081
|
||||
npm run dev:infra # http://localhost:8082
|
||||
npm run dev:smarthome # http://localhost:8083
|
||||
npm run dev:code # http://localhost:8084
|
||||
npm run dev:retro # http://localhost:8085
|
||||
npm run dev:stefankoelle # http://localhost:8086
|
||||
|
||||
npm run dev # all 6 in parallel
|
||||
npm run dev # moonweb sites (localhost:8081)
|
||||
npm run dev:stefankoelle # stefankoelle.de (localhost:8086)
|
||||
npm run dev:timecapsule # timecapsule (localhost:8087)
|
||||
```
|
||||
|
||||
Each site has its own minimal Eleventy config (`<site>/eleventy.config.js`). Live reload is built in.
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
npm run prebuild # pre-build tasks (CV PDF)
|
||||
npm run build # builds all 5 → dist/<site>/
|
||||
npm run build:hub # build single site
|
||||
npm run prebuild:cv # pre-build tasks (CV PDF)
|
||||
npm run build # builds all moonweb sites (hub, infra, smarthome, code, retro)
|
||||
npm run build:stefankoelle # builds stefankoelle.de
|
||||
npm run build:timecapsule # builds timecapsule
|
||||
npm run build:moonweb # builds moonweb + timecapsule (for deployment)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Project Structure
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
moonweb-site/
|
||||
├── hub/ # 🏠 Central index & gateway
|
||||
├── infra/ # 🏗️ Infra overview + 3 detail pages
|
||||
│ ├── backup-strategy/
|
||||
│ ├── monitoring/
|
||||
│ └── dev-environment/
|
||||
├── smarthome/ # 🏡 Smart home overview + 6 detail pages
|
||||
│ ├── homematic-mqtt/
|
||||
│ ├── tasmota-energy/
|
||||
│ ├── balkonpi/
|
||||
│ ├── airplay-audio/
|
||||
│ ├── octoprint/
|
||||
│ └── tubearchivist/
|
||||
├── code/ # 💻 GitHub catalog
|
||||
│ └── _data/repos.json # populated by the aggregator
|
||||
├── retro/ # 🕹️ Retro hardware (WIP)
|
||||
├── stefankoelle/ # 👤 CV, career, personal site
|
||||
├── hub/ # Central index + redirect .htm files
|
||||
├── infra/ # Infra overview + 8 detail pages
|
||||
├── smarthome/ # Smart home overview + 9 detail pages
|
||||
├── code/ # GitHub catalog
|
||||
├── retro/ # Retro hardware + 13 detail pages
|
||||
├── timecapsule/ # 2000s retro design (Eleventy 2.x)
|
||||
│ ├── eleventy.config.js
|
||||
│ ├── index.njk # Onepager (CV, Projects, Languages)
|
||||
│ ├── cv-print.njk # CV-only for PDF generation
|
||||
│ ├── ledmatrix/ # LED Matrix WebServer documentation
|
||||
│ └── assets/ # CSS, JS, images, favicons
|
||||
├── shared/ # 🔧 Shared components
|
||||
│ ├── package.json
|
||||
│ └── src/
|
||||
├── stefankoelle/ # CV, career, personal site
|
||||
│ ├── eleventy.config.js
|
||||
│ ├── index.njk
|
||||
│ ├── cv-print.njk
|
||||
│ ├── ledmatrix/
|
||||
│ └── assets/
|
||||
├── shared/ # Shared components
|
||||
│ ├── _includes/
|
||||
│ │ ├── base.njk # base layout (header, site-switcher, footer)
|
||||
│ │ └── card-grid.njk # card-grid template with emoji support
|
||||
│ ├── base.css # shared CSS (layout, cards, typography)
|
||||
│ └── theme-*.css # accent colors per domain
|
||||
│ │ ├── card-grid.njk # card-grid template
|
||||
│ │ └── sitemap.njk # central sitemap template
|
||||
│ ├── base.css # shared CSS
|
||||
│ └── favicon/ # favicon SVGs per section
|
||||
├── _data/
|
||||
│ └── repos.json # populated by the GitHub aggregator
|
||||
├── scripts/
|
||||
│ └── github-aggregator/ # 🐍 Python: reads .moonweb.yml → repos.json
|
||||
│ ├── aggregate.py
|
||||
│ ├── example.moonweb.yml
|
||||
│ └── README.md
|
||||
│ ├── github-aggregator/ # Python: reads .moonweb.yml -> repos.json
|
||||
│ └── cloudflare/ # redirect setup for old subdomains
|
||||
├── .github/workflows/
|
||||
│ └── build-deploy.yml # ⚙️ CI/CD: build + deploy to Cloudflare
|
||||
├── DESIGN.md # 📋 Initial concept (German)
|
||||
├── SPEC.md # 📋 Full specification (English, 159 lines)
|
||||
├── PLAN.md # 📋 Implementation plan
|
||||
├── TODO.md # 📋 Open items & workflow
|
||||
└── package.json # npm scripts for dev/build
|
||||
│ ├── build-deploy-moonweb.yml # CI/CD: IONOS SFTP (www.moonweb.org)
|
||||
│ └── deploy-stefankoelle.yml # CI/CD: IONOS SFTP (stefankoelle.de)
|
||||
├── eleventy.config.js # single config for all moonweb sites
|
||||
├── .eleventyignore # excludes stefankoelle/, timecapsule/
|
||||
├── DESIGN.md
|
||||
├── SPEC.md
|
||||
├── PLAN.md
|
||||
├── TODO.md
|
||||
└── package.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 CI/CD Pipeline
|
||||
## CI/CD Pipeline
|
||||
|
||||
### Cloudflare Pages (moonweb sites)
|
||||
### IONOS SFTP (www.moonweb.org)
|
||||
|
||||
Defined in `.github/workflows/build-deploy.yml`:
|
||||
Defined in `.github/workflows/build-deploy-moonweb.yml`:
|
||||
|
||||
```
|
||||
push to main
|
||||
│
|
||||
├── Build (matrix: hub, infra, smarthome, code, retro)
|
||||
│ ├── checkout → setup-node (22) → npm ci
|
||||
│ ├── npm run build:<site>
|
||||
│ ├── validate dist/<site>/ exists & non-empty
|
||||
│ └── upload artifact (7-day retention)
|
||||
│
|
||||
└── Deploy (matrix: 5 Cloudflare Workers)
|
||||
├── download artifact
|
||||
├── generate wrangler.toml
|
||||
└── wrangler pages deploy
|
||||
|
|
||||
+-- Build
|
||||
| +-- checkout -> setup-node (22) -> npm ci
|
||||
| +-- npm run build (all moonweb sites in one Eleventy run)
|
||||
| +-- npm run build:timecapsule
|
||||
|
|
||||
+-- Deploy
|
||||
+-- SFTP upload to IONOS /websites/moonweb/
|
||||
```
|
||||
|
||||
**Required secrets:**
|
||||
- `CLOUDFLARE_API_TOKEN` — Workers:Edit permission
|
||||
- `CLOUDFLARE_ACCOUNT_ID` — Cloudflare account ID
|
||||
- `IONOS_SFTP_HOST`
|
||||
- `IONOS_SFTP_USER`
|
||||
- `IONOS_SFTP_PASSWORD`
|
||||
|
||||
### IONOS SFTP (stefankoelle.de)
|
||||
|
||||
@@ -184,12 +159,12 @@ Defined in `.github/workflows/deploy-stefankoelle.yml`:
|
||||
|
||||
```
|
||||
push to main (paths: stefankoelle/**)
|
||||
│
|
||||
├── Build stefankoelle
|
||||
│ └── npm run build:stefankoelle
|
||||
│
|
||||
└── Deploy via SFTP
|
||||
└── lftp mirror → IONOS /deploy/stefankoelle/
|
||||
|
|
||||
+-- Build stefankoelle
|
||||
| +-- npm run build:stefankoelle
|
||||
|
|
||||
+-- Deploy via SFTP
|
||||
+-- SFTP upload to IONOS /websites/stefankoelle/
|
||||
```
|
||||
|
||||
**Required secrets:**
|
||||
@@ -199,7 +174,30 @@ push to main (paths: stefankoelle/**)
|
||||
|
||||
---
|
||||
|
||||
## 🐍 GitHub Aggregator (code.moonweb.org)
|
||||
## URL Structure
|
||||
|
||||
All moonweb.org sites are accessible under `www.moonweb.org` as subdirectories:
|
||||
|
||||
| URL | Content |
|
||||
|-----|---------|
|
||||
| `www.moonweb.org/` | Hub (root) |
|
||||
| `www.moonweb.org/infra/` | Infrastructure |
|
||||
| `www.moonweb.org/smarthome/` | Smart Home |
|
||||
| `www.moonweb.org/code/` | Code catalog |
|
||||
| `www.moonweb.org/retro/` | Retro hardware |
|
||||
| `www.moonweb.org/timecapsule/` | 2000s time capsule |
|
||||
| `www.moonweb.org/impressum/` | Legal notice |
|
||||
|
||||
Cloudflare redirects forward old subdomains:
|
||||
- `hub.moonweb.org/*` -> `www.moonweb.org/*`
|
||||
- `infra.moonweb.org/*` -> `www.moonweb.org/infra/*`
|
||||
- `smarthome.moonweb.org/*` -> `www.moonweb.org/smarthome/*`
|
||||
- `code.moonweb.org/*` -> `www.moonweb.org/code/*`
|
||||
- `retro.moonweb.org/*` -> `www.moonweb.org/retro/*`
|
||||
|
||||
---
|
||||
|
||||
## GitHub Aggregator (www.moonweb.org/code/)
|
||||
|
||||
`scripts/github-aggregator/aggregate.py` automatically builds the project catalog:
|
||||
|
||||
@@ -207,14 +205,14 @@ push to main (paths: stefankoelle/**)
|
||||
2. Reads `.moonweb.yml` from each repo root
|
||||
3. Filters for `category: code` entries
|
||||
4. Sorts by subcategory + title
|
||||
5. Writes combined result to `code/_data/repos.json`
|
||||
5. Writes combined result to `_data/repos.json`
|
||||
|
||||
### `.moonweb.yml` schema
|
||||
|
||||
```yaml
|
||||
title: "MVG Departures"
|
||||
category: code # code | smarthome | infra
|
||||
subcategory: "Web Apps" # drives grouping on code.moonweb.org
|
||||
subcategory: "Web Apps" # drives grouping on www.moonweb.org/code/
|
||||
status: active
|
||||
stack: [Python, FastAPI]
|
||||
hosted_on: "Docker Host Debian (PVE)"
|
||||
@@ -225,46 +223,47 @@ repo_url: "https://github.com/skoelle/mvg-departures"
|
||||
### Manual run
|
||||
|
||||
```bash
|
||||
export GITHUB_TOKEN=ghp_xxx
|
||||
python scripts/github-aggregator/aggregate.py
|
||||
python3 -m venv .venv
|
||||
.venv/bin/pip install pyyaml
|
||||
.venv/bin/python scripts/github-aggregator/aggregate.py
|
||||
```
|
||||
|
||||
> **Deliberately manual** — no scheduled CI job. The catalog is refreshed on demand, not on every push.
|
||||
|
||||
---
|
||||
|
||||
## 📏 Content Rules
|
||||
## Content Rules
|
||||
|
||||
| Site | Detail pages? | Rule |
|
||||
|------|--------------|------|
|
||||
| smarthome | ✅ Yes | When enough content exists — no placeholder cards |
|
||||
| infra | ⚠️ Rarely | Deliberately shallow — sensitive data (IPs, keys, passwords) stripped |
|
||||
| code | ❌ Never | Overview cards + GitHub links only — no README duplication |
|
||||
| retro | 🔨 Minimal | Still WIP — honest minimal overview, no over-investment |
|
||||
| smarthome | Yes | When enough content exists - no placeholder cards |
|
||||
| infra | Rarely | Deliberately shallow - sensitive data (IPs, keys, passwords) stripped |
|
||||
| code | Never | Overview cards + GitHub links only - no README duplication |
|
||||
| retro | Minimal | Honest minimal overview, no over-investment |
|
||||
| timecapsule | Static | 1:1 migration of original 2001 design, no changes |
|
||||
|
||||
**Infra redaction rule:** Architecture-level only (Proxmox, Synology, Docker, VLAN concept). No concrete IPs, WireGuard keys, passwords, internal hostnames.
|
||||
|
||||
---
|
||||
|
||||
## 🌍 Language
|
||||
## Language
|
||||
|
||||
All five sites are written **entirely in English**. German source documents are translated once during migration (AI-assisted). New content is authored in English from the start.
|
||||
All moonweb sites are written **entirely in English**. The timecapsule uses the original 2001 English content.
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation
|
||||
## Documentation
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `DESIGN.md` | Initial concept and design decisions (German) |
|
||||
| `SPEC.md` | Complete specification — what gets built (English) |
|
||||
| `DESIGN.md` | Initial concept and design decisions |
|
||||
| `SPEC.md` | Complete specification - what gets built |
|
||||
| `PLAN.md` | Phased implementation plan |
|
||||
| `TODO.md` | Open items, workflow, and current status |
|
||||
| `README.md` | This file — project overview for GitHub |
|
||||
| `README.md` | This file - project overview for GitHub |
|
||||
| `AGENTS.md` | AI agent instructions for this codebase |
|
||||
|
||||
---
|
||||
|
||||
## 📄 License
|
||||
## License
|
||||
|
||||
[](https://creativecommons.org/licenses/by-nc-sa/4.0/)
|
||||
|
||||
|
||||
@@ -1,63 +1,69 @@
|
||||
# moonweb.org — Specification
|
||||
|
||||
Status: Concept finalized. This document defines *what* gets built. See `PLAN.md` for *how and in what order*.
|
||||
Status: Migration complete. All sites live under `www.moonweb.org` as subdirectories, deployed via IONOS SFTP.
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
A personal homelab hub consisting of a central index (`hub`) and several themed static sites, replacing the current unstructured presentation of infrastructure, smart home projects, and GitHub repos. cv (stefankoelle.de) remains external, linked from the hub and site-switcher.
|
||||
A personal homelab hub consisting of a central index (`hub` at root) and several themed static sites, all served under `www.moonweb.org` as subdirectories. The old timecapsule content (2001 design) is preserved under `/timecapsule/`. cv (stefankoelle.de) remains external, linked from the hub and site-switcher.
|
||||
|
||||
## 2. Sitemap
|
||||
|
||||
```
|
||||
moonweb-site (monorepo)
|
||||
├── hub/ → hub.moonweb.org Central index & gateway
|
||||
├── infra/ → infra.moonweb.org System architecture / stack overview
|
||||
├── smarthome/ → smarthome.moonweb.org What the homelab actually runs, and why
|
||||
├── code/ → code.moonweb.org Curated GitHub catalog
|
||||
├── retro/ → retro.moonweb.org Physical retro hardware collection
|
||||
└── stefankoelle/→ stefankoelle.de CV, career, personal site (SFTP deploy)
|
||||
├── hub/ → www.moonweb.org/ Central index & gateway
|
||||
├── infra/ → www.moonweb.org/infra/ System architecture / stack overview
|
||||
├── smarthome/ → www.moonweb.org/smarthome/ What the homelab actually runs, and why
|
||||
├── code/ → www.moonweb.org/code/ Curated GitHub catalog
|
||||
├── retro/ → www.moonweb.org/retro/ Physical retro hardware collection
|
||||
├── timecapsule/ → www.moonweb.org/timecapsule/ 2001-era internet time capsule
|
||||
└── stefankoelle/→ stefankoelle.de CV, career, personal site (SFTP deploy)
|
||||
|
||||
outside the monorepo, untouched:
|
||||
├── www.moonweb.org finished, no overlap (2001-style internet time capsule)
|
||||
├── 28k8.moonweb.org 90s BBS/scene archive, separate approach, may migrate later (undecided)
|
||||
|
||||
├── 28k8.moonweb.org 90s BBS/scene archive
|
||||
├── buildbroken.moonweb.org .NET Open Space blog archive
|
||||
```
|
||||
|
||||
Apex domain `moonweb.org` currently redirects to `www.moonweb.org`. This stays as-is for now; a later redirect to `hub.moonweb.org` is possible but out of scope for this build.
|
||||
Old subdomain redirects (via Cloudflare):
|
||||
- `hub.moonweb.org/*` → `www.moonweb.org/*`
|
||||
- `infra.moonweb.org/*` → `www.moonweb.org/infra/*`
|
||||
- `smarthome.moonweb.org/*` → `www.moonweb.org/smarthome/*`
|
||||
- `code.moonweb.org/*` → `www.moonweb.org/code/*`
|
||||
- `retro.moonweb.org/*` → `www.moonweb.org/retro/*`
|
||||
|
||||
## 3. Domain purposes
|
||||
|
||||
| Domain | Purpose | Tone |
|
||||
|---|---|---|
|
||||
| hub | Gateway, links to everything, one-line description per destination | Minimal |
|
||||
| infra | Shallow, structured overview of the stack: Proxmox (PVE + pve2), Synology DS918+, VLANs, Fritz!Box mesh, SSO, plus how I work (OpenCode/OpenClaw dev environment) | Reference, high-level only |
|
||||
| smarthome | Why the homelab exists — what's actually running on the homelab as smart home: sensors, automation, calendar/contacts sync, dashboards, media | Project storytelling |
|
||||
| code | Curated, sorted GitHub catalog — overview only, always linking out to GitHub | Portfolio |
|
||||
| retro | Physical retro hardware collection (not software/demos — that's 28k8's domain) | Simple, factual |
|
||||
| Domain | URL | Purpose | Tone |
|
||||
|---|---|---|---|
|
||||
| hub | `/` | Gateway, links to everything, one-line description per destination | Minimal |
|
||||
| infra | `/infra/` | Shallow, structured overview of the stack: Proxmox, Synology, VLANs, Docker hosting, dev environment | Reference, high-level only |
|
||||
| smarthome | `/smarthome/` | Why the homelab exists — sensors, automation, calendar/contacts sync, dashboards, media | Project storytelling |
|
||||
| code | `/code/` | Curated, sorted GitHub catalog — overview only, always linking out to GitHub | Portfolio |
|
||||
| retro | `/retro/` | Physical retro hardware collection (not software/demos — that's 28k8's domain) | Simple, factual |
|
||||
| timecapsule | `/timecapsule/` | Original www.moonweb.org content from 2001, preserved as-is | Retro 2001 design |
|
||||
|
||||
## 4. Design system
|
||||
|
||||
### 4.1 Header-consistent, content-flexible principle
|
||||
|
||||
- **Header is identical** across infra/smarthome/code/retro: site-switcher (hub · infra · smarthome · code · retro), domain accent color, consistent branding.
|
||||
- **Overview (index) pages** use a shared card-grid layout (reference: clean card-grid layout with banner header, grouped card sections, sans-serif, generous whitespace, light theme only, no heavy JS).
|
||||
- **Detail/sub-pages** keep the same header but may use a freer layout below it (reference: `/ledmatrix/` on stefankoelle.de today — pin tables, API docs, photos in free layout instead of a rigid card grid).
|
||||
- **Header is identical** across all sites: site-switcher (home · infra · smarthome · code · retro · cv), section title (`moonweb.org` or `moonweb.org/smarthome`), domain accent color.
|
||||
- **Overview (index) pages** use a shared card-grid layout (clean card-grid with banner header, grouped card sections, sans-serif, generous whitespace, light theme only, no heavy JS).
|
||||
- **Detail/sub-pages** keep the same header but may use a freer layout below it.
|
||||
|
||||
Note: stefankoelle.de is now part of the monorepo but uses its own independent design — it does not follow this header-consistent principle.
|
||||
Note: stefankoelle.de uses its own independent design — it does not follow this header-consistent principle.
|
||||
|
||||
### 4.2 Accent colors per domain
|
||||
|
||||
| Domain | Accent |
|
||||
|---|---|
|
||||
| hub | Neutral blue |
|
||||
| infra | Grey-blue |
|
||||
| smarthome | Teal |
|
||||
| code | Violet |
|
||||
| retro | Own accent, still clean card-grid (no 90s styling — that belongs to 28k8) |
|
||||
| Domain | Accent | Implementation |
|
||||
|---|---|---|
|
||||
| hub | Neutral blue (#3b6ea5) | Inlined `<style>` in base.njk |
|
||||
| infra | Red (#99333A) | Inlined `<style>` in base.njk |
|
||||
| smarthome | Teal (#1f8a8a) | Inlined `<style>` in base.njk |
|
||||
| code | Violet (#3E5098) | Inlined `<style>` in base.njk |
|
||||
| retro | Brown (#8a6d3b) | Inlined `<style>` in base.njk |
|
||||
|
||||
### 4.3 URL convention
|
||||
|
||||
`domain/slug/` — lowercase, hyphenated, trailing slash, matching the existing stefankoelle.de pattern (e.g. `/ledmatrix/`) so future detail pages stay consistent even if content later migrates between sites.
|
||||
`/section/slug/` — lowercase, hyphenated, trailing slash. All sites share a single Eleventy build with computed `pathPrefix` per section.
|
||||
|
||||
## 5. Content depth rules (per domain)
|
||||
|
||||
@@ -77,20 +83,20 @@ Because infra must stay shallow and public-safe:
|
||||
- **Allowed:** architecture level — Proxmox + Synology + Docker host, VLAN concept without concrete internal IP plans, which service types run, which tools are used.
|
||||
- **Not allowed:** concrete IP addresses, WireGuard keys/preshared keys, passwords, internal hostnames that allow inference, backup targets with credentials.
|
||||
|
||||
This rule applies to any domain but is most relevant for infra, since the source documents (e.g. Heimnetzwerk-Final-v3.2, GL_Flint2_Final_v2) currently contain real IPs and keys that must be actively stripped during migration.
|
||||
This rule applies to any domain but is most relevant for infra, since the source documents currently contain real IPs and keys that must be actively stripped during migration.
|
||||
|
||||
## 7. Language
|
||||
|
||||
All sites in the monorepo are written **entirely in English**. Existing German source documents are translated once during migration via a single AI-assisted pass — not a recurring process. New `.moonweb.yml` metadata and generated content are authored in English from the start.
|
||||
|
||||
## 8. GitHub automation (code.moonweb.org)
|
||||
## 8. GitHub automation (www.moonweb.org/code/)
|
||||
|
||||
Each GitHub project repo gets a `.moonweb.yml` in its root:
|
||||
|
||||
```yaml
|
||||
title: "MVG Departures"
|
||||
category: code # code | smarthome | infra
|
||||
subcategory: "Web Apps" # drives grouping on code.moonweb.org
|
||||
subcategory: "Web Apps" # drives grouping on www.moonweb.org/code/
|
||||
status: active
|
||||
stack: [Python, FastAPI]
|
||||
hosted_on: "Docker Host Debian (PVE)"
|
||||
@@ -98,7 +104,7 @@ summary: "Compact MVG/S-Bahn departure monitor with configurable stations."
|
||||
repo_url: "https://github.com/skoelle/mvg-departures"
|
||||
```
|
||||
|
||||
A local aggregator script reads `.moonweb.yml` from all repos via the GitHub API, an AI pass turns the raw YAML into readable card copy, and the result is committed into `code/_data/repos.json` inside the monorepo. This runs manually, on demand — no scheduled automation for now.
|
||||
A local aggregator script reads `.moonweb.yml` from all repos via the GitHub API, and the result is committed into `_data/repos.json` inside the monorepo. This runs manually, on demand — no scheduled automation for now.
|
||||
|
||||
## 9. Content maintenance
|
||||
|
||||
@@ -112,47 +118,60 @@ A local aggregator script reads `.moonweb.yml` from all repos via the GitHub API
|
||||
|
||||
```
|
||||
moonweb-site/
|
||||
├── hub/
|
||||
├── infra/
|
||||
├── smarthome/
|
||||
├── code/
|
||||
│ └── _data/repos.json # populated by the GitHub aggregator
|
||||
├── retro/
|
||||
├── stefankoelle/ # CV, career, personal site
|
||||
├── hub/ # Index + Redirects (.htm) + impressum.njk
|
||||
├── infra/ # Index.njk + 8 Subseiten
|
||||
├── smarthome/ # Index.njk + 9 Subseiten
|
||||
├── code/ # Index.njk
|
||||
├── retro/ # Index.njk + 13 Subseiten
|
||||
├── timecapsule/ # Eleventy 2.x (eigene Config)
|
||||
│ ├── eleventy.config.js
|
||||
│ ├── index.njk # Onepager (CV, Projects, Languages)
|
||||
│ ├── cv-print.njk # CV-only for PDF generation
|
||||
│ ├── ledmatrix/ # LED Matrix WebServer documentation
|
||||
│ └── assets/ # CSS, JS, images, favicons
|
||||
├── shared/
|
||||
│ ├── package.json
|
||||
│ └── src/
|
||||
├── stefankoelle/ # Eleventy-Config + Onepager
|
||||
│ ├── eleventy.config.js
|
||||
│ ├── index.njk
|
||||
│ ├── cv-print.njk
|
||||
│ ├── ledmatrix/
|
||||
│ └── assets/
|
||||
├── shared/ # Shared components
|
||||
│ ├── _includes/
|
||||
│ │ ├── base.njk # shared header + footer layout
|
||||
│ │ └── card-grid.njk # card-grid template
|
||||
│ ├── base.css # shared CSS variables and layout
|
||||
│ └── theme-*.css # one accent color file per domain
|
||||
│ │ ├── base.njk # base layout (header, site-switcher, footer)
|
||||
│ │ ├── card-grid.njk # card-grid template
|
||||
│ │ └── sitemap.njk # central sitemap template
|
||||
│ ├── base.css # shared CSS
|
||||
│ └── favicon/ # favicon SVGs per section
|
||||
├── _data/
|
||||
│ └── repos.json # populated by the GitHub aggregator
|
||||
├── scripts/
|
||||
│ └── github-aggregator/ # reads .moonweb.yml from all repos
|
||||
├── DESIGN.md # initial concept and design decisions
|
||||
├── SPEC.md # what gets built (this document)
|
||||
├── PLAN.md # how and in what order
|
||||
├── TODO.md # open items and workflow
|
||||
└── .github/workflows/
|
||||
├── build-deploy.yml # builds all sites, deploys to Cloudflare Pages
|
||||
└── deploy-stefankoelle.yml # builds stefankoelle, deploys via IONOS SFTP
|
||||
│ ├── github-aggregator/ # reads .moonweb.yml from all repos
|
||||
│ └── cloudflare/ # redirect setup for old subdomains
|
||||
├── .github/workflows/
|
||||
│ ├── build-deploy-moonweb.yml # builds all moonweb sites, deploys to IONOS SFTP
|
||||
│ └── deploy-stefankoelle.yml # builds stefankoelle, deploys via IONOS SFTP
|
||||
├── eleventy.config.js # single Eleventy config for all moonweb sites
|
||||
├── .eleventyignore # excludes stefankoelle/, timecapsule/
|
||||
├── DESIGN.md
|
||||
├── SPEC.md
|
||||
├── PLAN.md
|
||||
├── TODO.md
|
||||
└── package.json
|
||||
```
|
||||
|
||||
## 11. Technical stack
|
||||
|
||||
- **Static site generator:** Eleventy (11ty) — markdown/YAML-first, minimal JS, `_data` folders map directly onto the `.moonweb.yml` aggregator output, low maintenance for five sites at this scale.
|
||||
- **Static site generator:** Eleventy (11ty) v3.1.6 — single config at root, computed `pathPrefix` per section, per-section collections.
|
||||
- **Static site generator (timecapsule):** Eleventy v2.0.1 — separate config, preserves original 2001 design.
|
||||
- **Templates:** Nunjucks (.njk) — shared `base.njk` layout, `card-grid.njk` for index pages, `sitemap.njk` for central sitemap.
|
||||
- **Styling:** Custom CSS (`base.css`) with accent colors inlined as `<style>` in `base.njk`. No per-site theme CSS files.
|
||||
- **Build:** entirely in GitHub Actions.
|
||||
- **Deploy:** Cloudflare Pages — five separate Pages projects (one per public domain: hub, infra, smarthome, code, retro), since Cloudflare Pages binds one custom-domain set per project. stefankoelle.de deploys via IONOS SFTP. Two GitHub Actions workflows handle deployment.
|
||||
- **Runtime:** fully static, no server-side code, no containers for the website itself (distinct from the actual homelab services running on the Docker host).
|
||||
- **DNS:** already on Cloudflare — no additional setup step needed for Pages custom domains.
|
||||
- **Local preview:** Eleventy's built-in dev server with live reload, run per-site (`npm run dev:<site>`) before any commit.
|
||||
- **Deploy:** IONOS SFTP — all sites deployed to `/websites/moonweb/`, stefankoelle.de to `/websites/stefankoelle/`.
|
||||
- **DNS:** Cloudflare — DNS management + redirects from old subdomains (hub.moonweb.org, etc.).
|
||||
- **Runtime:** fully static, no server-side code, no containers for the website itself.
|
||||
- **Local preview:** Eleventy's built-in dev server with live reload (`npm run dev` for all moonweb sites, `npm run dev:stefankoelle`, `npm run dev:timecapsule`).
|
||||
|
||||
## 12. Explicitly out of scope for this build
|
||||
## 12. Explicitly out of scope
|
||||
|
||||
- Migrating 28k8.moonweb.org into the monorepo — undecided, revisit later, likely never.
|
||||
- Any Perplexity-backchannel mechanism (website content reusable inside this project) — deferred, no time invested now.
|
||||
- Any Perplexity-backchannel mechanism — deferred.
|
||||
- Analytics of any kind.
|
||||
- Automated content generation/translation pipelines beyond the one-time GitHub aggregator for code and the one-time translation pass during migration.
|
||||
- Automated content generation/translation pipelines beyond the one-time GitHub aggregator for code.
|
||||
|
||||
@@ -1,76 +1,76 @@
|
||||
# TODO — moonweb-site Offene Items
|
||||
# TODO: moonweb-site
|
||||
|
||||
Stand: 2026-08-14
|
||||
## Status: Consolidation Complete
|
||||
|
||||
## Aktueller Status
|
||||
All moonweb.org sites are consolidated under `www.moonweb.org` as subdirectories, deployed via IONOS SFTP. PR #3 merged to main.
|
||||
|
||||
| Phase | Status | Bemerkung |
|
||||
|-------|--------|-----------|
|
||||
| Phase 0 — Repo & Tooling | ✅ fertig | Eleventy 3.1.6, CI/CD, .gitignore erweitert |
|
||||
| Phase 1 — Content | ✅ fertig | hub, code, infra, smarthome komplett, retro bewusst rudimentär |
|
||||
| Phase 2 — CI/CD | ✅ fertig | build-deploy.yml mit Caching + Validation + Summary |
|
||||
| Phase 3 — Launch | ✅ fertig | Cloudflare Pages live, Smoke-Test bestanden |
|
||||
| Phase 4 — Deferred | ⏸️ zurückgestellt | 28k8, Analytics |
|
||||
| Phase 5 — stefankoelle.de | ✅ fertig | Ins Monorepo migriert, Eleventy-Build, SFTP-Deployment |
|
||||
---
|
||||
|
||||
## Offene Items
|
||||
## Completed
|
||||
|
||||
### P4 — Deferred (nicht dringend)
|
||||
### Phase 1: Timecapsule Integration
|
||||
- [x] Copy files from moonweb-www/src/ to timecapsule/src/
|
||||
- [x] Create timecapsule/eleventy.config.js (Eleventy 2.x)
|
||||
- [x] Create timecapsule/package.json (own dependencies)
|
||||
- [x] Adapt timecapsule internal paths with /timecapsule/ prefix
|
||||
|
||||
- [ ] 28k8.moonweb.org Zukunft klären
|
||||
- [ ] Perplexity-Rückkanal
|
||||
- [ ] Apex-Domain Redirect (moonweb.org → hub.moonweb.org)
|
||||
- [ ] PDF-Build fuer CV (WeasyPrint, lokal testen)
|
||||
- [ ] Querverlinkungen stefankoelle.de <-> smarthome
|
||||
- [ ] Ledmatrix ggf. nach smarthome verschieben
|
||||
- [ ] **Dotnet Home-Automation-Projekt beschreiben** — Interne .NET Core Software für Kommunikation auf buttons.html und InfluxDB. Code muss erst aufgeraeumt und nach GitHub publiziert werden, dann Beschreibung auf stefankoelle.de und ggf. smarthome ergänzen.
|
||||
### Phase 2: Single Eleventy Config
|
||||
- [x] Create root eleventy.config.js with computed pathPrefix per section
|
||||
- [x] Delete 5 per-site eleventy.config.js files (hub, infra, smarthome, code, retro)
|
||||
- [x] Delete scripts/merge-moonweb.sh
|
||||
- [x] Update shared/_includes/base.njk (inlined accent colors, root CSS path)
|
||||
- [x] Update hub/index.njk card hrefs (relative paths)
|
||||
- [x] Update hub/impressum.njk (hosting sections correct)
|
||||
- [x] Update parent values in detail pages to include section prefix
|
||||
- [x] Add tags to all pages for Eleventy collections
|
||||
|
||||
### Offene Entscheidungen
|
||||
### Phase 3: Build & Deploy
|
||||
- [x] Create .github/workflows/build-deploy-moonweb.yml (single build job, SFTP deploy)
|
||||
- [x] Delete old build-deploy.yml (matrix builds)
|
||||
- [x] Update .eleventyignore (exclude stefankoelle/, timecapsule/, markdown)
|
||||
- [x] Fix build-pdf.sh to temporarily rename .eleventyignore for stefankoelle build
|
||||
- [x] Fix timecapsule build path (../dist/ not ../../dist/)
|
||||
|
||||
- [ ] **stefankoelle.de SFTP: `delete_remote_files`?** — Derzeit werden beim Deploy nur Dateien hochgeloaden, alte bleiben liegen. Mit `delete_remote_files: true` waere jeder Deploy ein sauberes Image (alles weg + neu). Kurzer Ausfall moeglich. Entscheidung offen.
|
||||
### Phase 4: Redirects & SEO
|
||||
- [x] Create 20 redirect .htm files in hub/ for old www.moonweb.org paths
|
||||
- [x] Create central sitemap.xml (36 pages)
|
||||
- [x] Create single robots.txt at root
|
||||
- [x] Set up Cloudflare redirect rules script (scripts/cloudflare/)
|
||||
|
||||
## Schon erledigt (Stand 2026-08-14)
|
||||
### Phase 5: Bug Fixes
|
||||
- [x] Fix card links on index pages (add section prefix to local hrefs)
|
||||
- [x] Fix code repos missing (move repos.json to root _data/)
|
||||
- [x] Fix favicons (use section-specific favicon.svg per site)
|
||||
- [x] Fix header (show moonweb.org/smarthome instead of smarthome.moonweb.org)
|
||||
- [x] Rename hub link to home in site-switcher nav
|
||||
- [x] Remove redundant 404 pages, keep only root 404
|
||||
- [x] Add missing beginning subpage redirects (news, report, sitemap)
|
||||
- [x] Fix double-slash URLs in stefankoelle/index.njk
|
||||
- [x] Remove unused theme-*.css files
|
||||
- [x] Update stefankoelle link texts (replace old subdomain names with new paths)
|
||||
|
||||
### stefankoelle.de Migration (2026-08-14)
|
||||
- [x] Eleventy-Config + package.json erweitert
|
||||
- [x] Onepager-Layout mit Anchor-Links beibehalten
|
||||
- [x] CV-Partial (zentral fuer Web + PDF)
|
||||
- [x] CSS ausgelagert + modernisiert (Custom Properties, box-sizing)
|
||||
- [x] Assets in assets/ verschoben
|
||||
- [x] Ledmatrix als eigene Seite integriert
|
||||
- [x] Alte Dateien geloescht (index.html, browserconfig.xml, sitemap.txt)
|
||||
- [x] Deployment-Workflow fuer IONOS SFTP
|
||||
- [x] Cloudflare-Workflow um stefankoelle erweitert (paths-ignore)
|
||||
### Phase 6: Documentation
|
||||
- [x] Update AGENTS.md for new structure
|
||||
- [x] Update README.md for new structure
|
||||
- [x] Update SPEC.md for new structure
|
||||
- [x] Update PLAN.md for new structure
|
||||
|
||||
### Vorherige Erledigungen (Stand 2026-08-13)
|
||||
- [x] Impressum angelegt + Footer-Link auf allen Sites
|
||||
- [x] Favicons (SVG mit Emojis) pro Subdomain — `shared/favicon/*.svg`, per Passthrough Copy
|
||||
- [x] Meta descriptions auf allen 33 Seiten
|
||||
- [x] CSS @import-Kette bereinigt (kein Render-Blocking mehr)
|
||||
- [x] .gitignore erweitert (.env*, Zertifikate, Swap-Files)
|
||||
- [x] Placeholder-Cards `"#"` bereinigt — keine mehr vorhanden
|
||||
- [x] README.md überarbeitet (Emojis, Architektur, Tech-Stack, CI/CD)
|
||||
- [x] Lizenz hinzugefügt (CC BY-NC-SA 4.0)
|
||||
- [x] Content-Review — alle Seiten geprüft, alles passt
|
||||
- [x] Cloudflare Pages Projects erstellt (moonweb-hub, -infra, -smarthome, -code, -retro)
|
||||
- [x] DNS & Custom Domains gebunden
|
||||
- [x] Secrets konfiguriert (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID)
|
||||
- [x] Smoke-Test Cross-Linking — alles funktioniert auf live
|
||||
---
|
||||
|
||||
## Workflow für neue Contexts
|
||||
## Remaining
|
||||
|
||||
Wenn du einen neuen Context öffnest, lies diese Datei und prüfe:
|
||||
1. Welche P4-Items sind relevant? → Nur wenn angefordert
|
||||
### Cloudflare Redirects
|
||||
- [ ] Activate redirect rules (needs `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ZONE_ID` secrets, or manual dashboard setup)
|
||||
- [ ] Verify redirects work after activation
|
||||
|
||||
## Definition of done
|
||||
### SEO
|
||||
- [ ] Create Google Search Console property for www.moonweb.org
|
||||
- [ ] Submit sitemap
|
||||
|
||||
Alle 5 Sites live auf Cloudflare Pages:
|
||||
- ✅ hub.moonweb.org — verlinkt alles (+ stefankoelle.de extern)
|
||||
- ✅ code.moonweb.org — GitHub-Katalog via Aggregator
|
||||
- ✅ smarthome.moonweb.org — Smart Home Übersicht + Detailseiten
|
||||
- ✅ infra.moonweb.org — Infra-Übersicht (redigiert)
|
||||
- ✅ retro.moonweb.org — Ehrliche minimale Übersicht (WIP)
|
||||
### Cleanup
|
||||
- [ ] Archive moonweb-www repository
|
||||
- [ ] Delete Cloudflare Pages projects (after redirect verification)
|
||||
|
||||
stefankoelle.de live auf IONOS (SFTP-Deployment):
|
||||
- ✅ stefankoelle.de — Onepager mit CV, Projects, Languages, Impressum
|
||||
- ✅ stefankoelle.de/ledmatrix/ — LED Matrix WebServer Dokumentation
|
||||
### Future
|
||||
- [ ] Cross-linking stefankoelle.de <-> smarthome
|
||||
- [ ] Consider moving LED Matrix to smarthome (when ready)
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
title: "Page Not Found"
|
||||
section: "code"
|
||||
description: "The page you are looking for does not exist."
|
||||
layout: base.njk
|
||||
permalink: /404.html
|
||||
---
|
||||
<div class="error-page">
|
||||
<h1>404</h1>
|
||||
<p>The page you are looking for does not exist or has been moved.</p>
|
||||
<a class="back-link" href="/">← Back to code</a>
|
||||
</div>
|
||||
@@ -1,20 +0,0 @@
|
||||
module.exports = function (eleventyConfig) {
|
||||
eleventyConfig.addGlobalData("site", { url: "https://code.moonweb.org" });
|
||||
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,4 +0,0 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://code.moonweb.org/sitemap.xml
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
permalink: /sitemap.xml
|
||||
eleventyExcludeFromCollections: true
|
||||
---
|
||||
{% include "sitemap.njk" %}
|
||||
@@ -0,0 +1,97 @@
|
||||
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" });
|
||||
eleventyConfig.addPassthroughCopy({ "hub/robots.txt": "robots.txt" });
|
||||
|
||||
// Per-section assets: favicon
|
||||
for (const [section, cfg] of Object.entries(SITES)) {
|
||||
const prefix = cfg.pathPrefix || ".";
|
||||
eleventyConfig.addPassthroughCopy({
|
||||
[`shared/favicon/${section}.svg`]: `${prefix}/favicon.svg`,
|
||||
});
|
||||
}
|
||||
|
||||
// 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`,
|
||||
});
|
||||
}
|
||||
const beginningSubpages = ["news", "report", "sitemap"];
|
||||
for (const name of beginningSubpages) {
|
||||
eleventyConfig.addPassthroughCopy({
|
||||
[`hub/beginning/${name}.htm`]: `beginning/${name}.html`,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
dir: {
|
||||
input: ".",
|
||||
includes: "shared/_includes",
|
||||
output: "dist/moonweb",
|
||||
},
|
||||
serverOptions: {
|
||||
host: "0.0.0.0",
|
||||
port: 8081,
|
||||
},
|
||||
};
|
||||
};
|
||||
+2
-1
@@ -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
|
||||
@@ -8,5 +9,5 @@ permalink: /404.html
|
||||
<div class="error-page">
|
||||
<h1>404</h1>
|
||||
<p>The page you are looking for does not exist or has been moved.</p>
|
||||
<a class="back-link" href="/">← Back to hub</a>
|
||||
<a class="back-link" href="/">← Back to home</a>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/acoustics/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/acoustics/">/timecapsule/acoustics/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/background/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/background/">/timecapsule/background/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/beginning/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/beginning/">/timecapsule/beginning/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/beginning/news.html">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/beginning/news.html">/timecapsule/beginning/news.html</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/beginning/report.html">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/beginning/report.html">/timecapsule/beginning/report.html</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/beginning/sitemap.html">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/beginning/sitemap.html">/timecapsule/beginning/sitemap.html</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/contact/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/contact/">/timecapsule/contact/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,21 +0,0 @@
|
||||
module.exports = function (eleventyConfig) {
|
||||
eleventyConfig.addGlobalData("site", { url: "https://hub.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" });
|
||||
|
||||
return {
|
||||
dir: {
|
||||
input: "hub",
|
||||
includes: "../shared/_includes",
|
||||
output: "dist/hub"
|
||||
},
|
||||
serverOptions: {
|
||||
host: "0.0.0.0",
|
||||
port: 8081
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/">/timecapsule/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
+16
-9
@@ -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
|
||||
---
|
||||
@@ -19,7 +21,7 @@ layout: base.njk
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Scope of this legal notice: This legal notice and the privacy policy below apply to the following websites, all operated by Stefan Kölle: hub.moonweb.org, smarthome.moonweb.org, infra.moonweb.org, code.moonweb.org, retro.moonweb.org, buildbroken.moonweb.org, 28k8.moonweb.org, www.moonweb.org, and stefankoelle.de. All of these sites are part of the same personal project network and are covered jointly by this single document.
|
||||
Scope of this legal notice: This legal notice and the privacy policy below apply to the following websites, all operated by Stefan Kölle: www.moonweb.org (including /infra/, /smarthome/, /code/, /retro/, /timecapsule/), buildbroken.moonweb.org, 28k8.moonweb.org, and stefankoelle.de. All of these sites are part of the same personal project network and are covered jointly by this single document.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
@@ -41,7 +43,7 @@ layout: base.njk
|
||||
|
||||
<h3>2. Purpose of these Websites</h3>
|
||||
<p>
|
||||
The moonweb.org subdomains (hub, smarthome, infra, code, retro) document my private home-lab, home-automation and retro-computing hobby projects. buildbroken.moonweb.org is an archive of a blog I created with friends in our spare time. 28k8.moonweb.org is a homage to the 90s era. www.moonweb.org is an archived version of my original website from 2001. stefankoelle.de presents my professional CV and background as the same person. All are part of one personal project network and share this legal notice and privacy policy.
|
||||
The moonweb.org websites (home, smarthome, infra, code, retro, timecapsule) document my private home-lab, home-automation and retro-computing hobby projects. buildbroken.moonweb.org is an archive of a blog I created with friends in our spare time. 28k8.moonweb.org is a homage to the 90s era. stefankoelle.de presents my professional CV and background as the same person. All are part of one personal project network and share this legal notice and privacy policy.
|
||||
</p>
|
||||
|
||||
<h3>3. Hosting</h3>
|
||||
@@ -49,18 +51,23 @@ layout: base.njk
|
||||
Two different hosting providers are used within this network:
|
||||
</p>
|
||||
|
||||
<h4>a) moonweb.org subdomains (hub, smarthome, infra, code, retro, buildbroken) — Cloudflare Pages</h4>
|
||||
<p>
|
||||
Hosted via Cloudflare Pages, operated by Cloudflare, Inc., 101 Townsend St, San Francisco, CA 94107, USA. Cloudflare processes connection-level data (e.g. your IP address, request headers) as part of its content delivery and DDoS/security systems. This is governed by Cloudflare's Data Processing Addendum, which incorporates the EU Standard Contractual Clauses (2021) as a safeguard for the transfer of data to the USA. See <a href="https://www.cloudflare.com/privacypolicy/">cloudflare.com/privacypolicy</a>.
|
||||
</p>
|
||||
|
||||
<h4>b) www.moonweb.org and stefankoelle.de — IONOS SE</h4>
|
||||
<h4>a) www.moonweb.org (home, infra, smarthome, code, retro, timecapsule) — IONOS SE</h4>
|
||||
<p>
|
||||
Hosted on web space provided by IONOS SE, Elgendorfer Str. 57, 56410 Montabaur, Germany, acting as a processor under Art. 28 GDPR (Data Processing Agreement in place).
|
||||
</p>
|
||||
|
||||
<h4>b) stefankoelle.de — IONOS SE</h4>
|
||||
<p>
|
||||
Legal basis for both: Art. 6(1)(f) GDPR (legitimate interest in reliable, secure delivery of the websites).
|
||||
Hosted on web space provided by IONOS SE, Elgendorfer Str. 57, 56410 Montabaur, Germany, acting as a processor under Art. 28 GDPR (Data Processing Agreement in place).
|
||||
</p>
|
||||
|
||||
<h4>c) buildbroken.moonweb.org, 28k8.moonweb.org — Cloudflare Pages</h4>
|
||||
<p>
|
||||
Hosted via Cloudflare Pages, operated by Cloudflare, Inc., 101 Townsend St, San Francisco, CA 94107, USA. Cloudflare processes connection-level data (e.g. your IP address, request headers) as part of its content delivery and DDoS/security systems. This is governed by Cloudflare’s Data Processing Addendum, which incorporates the EU Standard Contractual Clauses (2021) as a safeguard for the transfer of data to the USA. See <a href="https://www.cloudflare.com/privacypolicy/">cloudflare.com/privacypolicy</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Legal basis: Art. 6(1)(f) GDPR (legitimate interest in reliable, secure delivery of the websites).
|
||||
</p>
|
||||
|
||||
<h3>4. No Tracking, No Cookies, No Analytics (current state)</h3>
|
||||
|
||||
+10
-8
@@ -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"
|
||||
@@ -9,19 +11,19 @@ sections:
|
||||
cards:
|
||||
- title: "infra"
|
||||
summary: "Stack overview: Proxmox, Synology, network, backups, and how I work."
|
||||
href: "https://infra.moonweb.org"
|
||||
href: "/infra/"
|
||||
emoji: "🏗️"
|
||||
- title: "smarthome"
|
||||
summary: "What's actually running on the homelab, and why."
|
||||
href: "https://smarthome.moonweb.org"
|
||||
href: "/smarthome/"
|
||||
emoji: "🏠"
|
||||
- title: "code"
|
||||
summary: "Curated catalog of my GitHub projects."
|
||||
href: "https://code.moonweb.org"
|
||||
href: "/code/"
|
||||
emoji: "💻"
|
||||
- title: "retro"
|
||||
summary: "Physical retro hardware collection."
|
||||
href: "https://retro.moonweb.org"
|
||||
href: "/retro/"
|
||||
emoji: "🕹️"
|
||||
- heading: "About me"
|
||||
cards:
|
||||
@@ -35,9 +37,9 @@ sections:
|
||||
summary: "tHE tEMPLE BBS Mailbox and 90s releases archive."
|
||||
href: "https://28k8.moonweb.org"
|
||||
emoji: "💾"
|
||||
- title: "www.moonweb.org"
|
||||
summary: "The 2000s internet projects, networking and engineering."
|
||||
href: "https://www.moonweb.org"
|
||||
- title: "moonweb.org 2001"
|
||||
summary: "The original www.moonweb.org as it was in 2001 — preserved as a time capsule."
|
||||
href: "/timecapsule/"
|
||||
emoji: "🌐"
|
||||
- title: "inseco.de"
|
||||
summary: "Internet services & consulting for gastronomy and clubs, Peak 2003–2008. Custom PHP/CMS."
|
||||
@@ -63,7 +65,7 @@ sections:
|
||||
<section class="about-expanded">
|
||||
<h2>What this site is about</h2>
|
||||
<p>moonweb is a collection of personal projects that have grown over the past 25 years. What started as a single 486 running a BBS in the mid-90s has turned into a full homelab with Proxmox, Docker, smart home automation, and a growing collection of retro hardware. The name "moonweb" comes from the original domain moonweb.org, which has been online since 2000.</p>
|
||||
<p>Each section of this site documents a different part of the setup. The <a href="https://infra.moonweb.org">infrastructure section</a> covers the Proxmox cluster, Synology NAS, Docker containers, networking, and backup strategies. The <a href="https://smarthome.moonweb.org">smart home section</a> shows what automation projects are running, from Tasmota energy monitoring to MQTT sensors and AirPlay audio. The <a href="https://code.moonweb.org">code section</a> lists the public GitHub projects that power these setups, and the <a href="https://retro.moonweb.org">retro section</a> documents the hardware collection.</p>
|
||||
<p>Each section of this site documents a different part of the setup. The <a href="/infra/">infrastructure section</a> covers the Proxmox cluster, Synology NAS, Docker containers, networking, and backup strategies. The <a href="/smarthome/">smart home section</a> shows what automation projects are running, from Tasmota energy monitoring to MQTT sensors and AirPlay audio. The <a href="/code/">code section</a> lists the public GitHub projects that power these setups, and the <a href="/retro/">retro section</a> documents the hardware collection.</p>
|
||||
|
||||
<h2>What I do professionally</h2>
|
||||
<p>I work as a Senior Software Developer & Architect at TENHIL GmbH (formerly stellenanzeigen.de), where I build and maintain microservices on .NET Core and Kubernetes. My day job involves designing cloud-native architectures, building APIs, and migrating legacy applications to modern stacks. I've been working with .NET since the early 2000s and have used everything from ASP Classic to the latest .NET 8 features.</p>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/">/timecapsule/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/">/timecapsule/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/">/timecapsule/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/legal-notice/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/legal-notice/">/timecapsule/legal-notice/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/">/timecapsule/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/location/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/location/">/timecapsule/location/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/network/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/network/">/timecapsule/network/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/">/timecapsule/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/partners/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/partners/">/timecapsule/partners/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/products/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/products/">/timecapsule/products/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/projects/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/projects/">/timecapsule/projects/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://hub.moonweb.org/sitemap.xml
|
||||
Sitemap: https://www.moonweb.org/sitemap.xml
|
||||
Sitemap: https://www.moonweb.org/timecapsule/sitemap.xml
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/">/timecapsule/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/stefan/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/stefan/">/timecapsule/stefan/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=/timecapsule/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/timecapsule/">/timecapsule/</a>...</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
title: "Page Not Found"
|
||||
section: "infra"
|
||||
description: "The page you are looking for does not exist."
|
||||
layout: base.njk
|
||||
permalink: /404.html
|
||||
---
|
||||
<div class="error-page">
|
||||
<h1>404</h1>
|
||||
<p>The page you are looking for does not exist or has been moved.</p>
|
||||
<a class="back-link" href="/">← Back to infra</a>
|
||||
</div>
|
||||
@@ -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://infra.moonweb.org" });
|
||||
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
|
||||
}
|
||||
};
|
||||
};
|
||||
+9
-8
@@ -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:
|
||||
@@ -8,30 +9,30 @@ sections:
|
||||
cards:
|
||||
- title: "🖥️ Proxmox host (PVE)"
|
||||
summary: "Hosting environment for running production VMs and LXC containers for the homelab."
|
||||
href: "/proxmox/"
|
||||
href: "/infra/proxmox/"
|
||||
- title: "💾 Synology DS918+"
|
||||
summary: "NAS running Docker services and acting as the primary backup repository."
|
||||
href: "/synology/"
|
||||
href: "/infra/synology/"
|
||||
- title: "🐳 Docker VMs (Debian)"
|
||||
summary: "Primary target for new Docker/Compose services, running the monitoring exporters."
|
||||
href: "/docker/"
|
||||
href: "/infra/docker/"
|
||||
- title: "📦 LxC Container"
|
||||
summary: "Pi-hole, Ubuntu-Worker and MariaDB running on slim LxC Containers."
|
||||
href: "/lxc/"
|
||||
href: "/infra/lxc/"
|
||||
- heading: "Strategies & Setup-Guides"
|
||||
cards:
|
||||
- title: "🔄 Backup strategy"
|
||||
summary: "Layered backup approach covering NAS user content and VM/LXC backups across multiple locations."
|
||||
href: "/backup-strategy/"
|
||||
href: "/infra/backup-strategy/"
|
||||
- title: "📊 Prometheus/Grafana"
|
||||
summary: "Central metrics collection and dashboards for hosts, containers, and hardware."
|
||||
href: "/monitoring/"
|
||||
href: "/infra/monitoring/"
|
||||
- title: "💻 Dev environment"
|
||||
summary: "A disposable, daily-backed-up Xubuntu VM for AI-assisted, remote development via SSH and Chrome Remote Desktop."
|
||||
href: "/dev-environment/"
|
||||
href: "/infra/dev-environment/"
|
||||
- title: "🌐 OpenWrt Multi-WAN"
|
||||
summary: "Multi-WAN router with WiFi and Powerline failover, plus IoT VLAN 189 isolation."
|
||||
href: "/openwrt/"
|
||||
href: "/infra/openwrt/"
|
||||
---
|
||||
<p class="site-intro">A peek under the hood of my homelab. Proxmox, Synology, Docker, and how everything is wired together.</p>
|
||||
|
||||
|
||||
+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,4 +0,0 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://infra.moonweb.org/sitemap.xml
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
permalink: /sitemap.xml
|
||||
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
|
||||
---
|
||||
|
||||
+9
-16
@@ -1,25 +1,18 @@
|
||||
{
|
||||
"name": "moonweb-site",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"private": true,
|
||||
"description": "Monorepo for hub/infra/smarthome/code/retro (Eleventy, static, deployed to Cloudflare Pages)",
|
||||
"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": "npm run dev:hub & npm run dev:infra & npm run dev:smarthome & npm run dev:code & npm run dev:retro & npm run dev:stefankoelle",
|
||||
"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:stefankoelle": "npx @11ty/eleventy --config=stefankoelle/eleventy.config.js",
|
||||
"build": "npm run build:hub && npm run build:infra && npm run build:smarthome && npm run build:code && npm run build:retro && npm run build:stefankoelle",
|
||||
"dev:timecapsule": "cd timecapsule && npx @11ty/eleventy --serve --port=8087",
|
||||
"build": "npx @11ty/eleventy",
|
||||
"build:stefankoelle": "mv .eleventyignore .eleventyignore.bak && npx @11ty/eleventy --config=stefankoelle/eleventy.config.js; mv .eleventyignore.bak .eleventyignore 2>/dev/null; true",
|
||||
"build:timecapsule": "cd timecapsule && npx @11ty/eleventy && mkdir -p ../dist/moonweb/timecapsule && cp -r _site/timecapsule/* ../dist/moonweb/timecapsule/",
|
||||
"build:moonweb": "npm run build && npm run build:timecapsule",
|
||||
"pdf:cv": "bash stefankoelle/pdf/build-pdf.sh",
|
||||
"prebuild": "bash prebuild.sh"
|
||||
"prebuild:cv": "bash prebuild.sh"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "^3.1.6"
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
title: "Page Not Found"
|
||||
section: "retro"
|
||||
description: "The page you are looking for does not exist."
|
||||
layout: base.njk
|
||||
permalink: /404.html
|
||||
---
|
||||
<div class="error-page">
|
||||
<h1>404</h1>
|
||||
<p>The page you are looking for does not exist or has been moved.</p>
|
||||
<a class="back-link" href="/">← Back to retro</a>
|
||||
</div>
|
||||
@@ -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://retro.moonweb.org" });
|
||||
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">
|
||||
|
||||
+14
-13
@@ -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:
|
||||
@@ -8,51 +9,51 @@ sections:
|
||||
cards:
|
||||
- title: "🖥️ The Mega ST Fleet"
|
||||
summary: "Three Mega ST 4 machines and a Mega ST 2, each with a clearly defined role from heirloom to workstation."
|
||||
href: "/atari-mega-st-fleet/"
|
||||
href: "/retro/atari-mega-st-fleet/"
|
||||
- title: "⚡ ET4000 Experiment"
|
||||
summary: "Why bolting a graphics card onto a Mega ST overloads a 1980s power supply, and what actually solved the resolution problem."
|
||||
href: "/et4000-experiment/"
|
||||
href: "/retro/et4000-experiment/"
|
||||
- title: "📺 Monitor Compatibility"
|
||||
summary: "The 15kHz problem explained, and which modern monitor panels actually cooperate with Atari colour modes."
|
||||
href: "/atari-monitor-compatibility/"
|
||||
href: "/retro/atari-monitor-compatibility/"
|
||||
- title: "🔌 Networking & BBS"
|
||||
summary: "Getting an Atari ST online via STinG and Wi-Fi modem hardware, and dialing into classic bulletin board systems."
|
||||
href: "/atari-networking-bbs/"
|
||||
href: "/retro/atari-networking-bbs/"
|
||||
- heading: "Amiga Corner"
|
||||
cards:
|
||||
- title: "💾 AmigaOS on Modern Platforms"
|
||||
summary: "Running AmigaOS 3.2.3 on MiSTer FPGA versus Amiberry on a Raspberry Pi, and the HDF pitfalls to avoid."
|
||||
href: "/amiga-modern-platforms/"
|
||||
href: "/retro/amiga-modern-platforms/"
|
||||
- title: "🥧 The PiMiga Journey"
|
||||
summary: "Three generations of the PiMiga distro compared, and why the newest release isn't automatically the best one."
|
||||
href: "/pimiga-journey/"
|
||||
href: "/retro/pimiga-journey/"
|
||||
- title: "⚔️ Vampire, A600GS or MiSTer"
|
||||
summary: "Why comparing modern Amiga hardware by benchmark numbers alone is actively misleading."
|
||||
href: "/amiga-hardware-landscape/"
|
||||
href: "/retro/amiga-hardware-landscape/"
|
||||
- title: "📦 The A1200"
|
||||
summary: "What's confirmed so far about Retro Games Ltd.'s upcoming Amiga 1200 console, ahead of its 2026 launch."
|
||||
href: "/the-a1200/"
|
||||
href: "/retro/the-a1200/"
|
||||
- heading: "FPGA & Emulation"
|
||||
cards:
|
||||
- title: "🧩 MiST and MiSTer"
|
||||
summary: "Two FPGA boards with two very different personalities, and why the newer one doesn't win every category."
|
||||
href: "/mist-mister-fpga/"
|
||||
href: "/retro/mist-mister-fpga/"
|
||||
- title: "🛋️ Batocera in the Living Room"
|
||||
summary: "The couch-friendly, software-emulation counterpart to the FPGA rigs, covering everything from Atari ST to arcade."
|
||||
href: "/batocera-living-room/"
|
||||
href: "/retro/batocera-living-room/"
|
||||
- title: "🕹️ Mini Arcades & Handhelds"
|
||||
summary: "A buyer's guide to compact retro gaming devices, and how to spot a bootleg clone before you buy one."
|
||||
href: "/mini-arcade-handheld-roundup/"
|
||||
href: "/retro/mini-arcade-handheld-roundup/"
|
||||
- heading: "DOS & PC"
|
||||
cards:
|
||||
- title: "💻 The 486 DX2-66"
|
||||
summary: "A complete DOS environment rebuilt 1:1 on MiSTer FPGA, down to the original AUTOEXEC.BAT."
|
||||
href: "/dos-486-tower/"
|
||||
href: "/retro/dos-486-tower/"
|
||||
- heading: "Overview"
|
||||
cards:
|
||||
- title: "🏠 The Retro Corner Today"
|
||||
summary: "A room-by-room snapshot of every retro system currently active across the home."
|
||||
href: "/retro-corner-snapshot/"
|
||||
href: "/retro/retro-corner-snapshot/"
|
||||
|
||||
---
|
||||
<p class="site-intro">Hardware I've collected and kept running over the years. From Atari ST and Amiga to MiSTer FPGA and DOS environments.</p>
|
||||
|
||||
@@ -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,4 +0,0 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://retro.moonweb.org/sitemap.xml
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
permalink: /sitemap.xml
|
||||
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">
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
[
|
||||
{
|
||||
"expression": "http.host eq \"hub.moonweb.org\"",
|
||||
"description": "hub.moonweb.org -> www.moonweb.org",
|
||||
"action": "redirect",
|
||||
"action_parameters": {
|
||||
"from_value": {
|
||||
"target_url": {
|
||||
"expression": "concat(\"https://www.moonweb.org\", http.request.uri.path)"
|
||||
},
|
||||
"status_code": 301,
|
||||
"preserve_query_string": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"expression": "http.host eq \"infra.moonweb.org\"",
|
||||
"description": "infra.moonweb.org -> www.moonweb.org/infra/",
|
||||
"action": "redirect",
|
||||
"action_parameters": {
|
||||
"from_value": {
|
||||
"target_url": {
|
||||
"expression": "concat(\"https://www.moonweb.org/infra\", http.request.uri.path)"
|
||||
},
|
||||
"status_code": 301,
|
||||
"preserve_query_string": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"expression": "http.host eq \"smarthome.moonweb.org\"",
|
||||
"description": "smarthome.moonweb.org -> www.moonweb.org/smarthome/",
|
||||
"action": "redirect",
|
||||
"action_parameters": {
|
||||
"from_value": {
|
||||
"target_url": {
|
||||
"expression": "concat(\"https://www.moonweb.org/smarthome\", http.request.uri.path)"
|
||||
},
|
||||
"status_code": 301,
|
||||
"preserve_query_string": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"expression": "http.host eq \"code.moonweb.org\"",
|
||||
"description": "code.moonweb.org -> www.moonweb.org/code/",
|
||||
"action": "redirect",
|
||||
"action_parameters": {
|
||||
"from_value": {
|
||||
"target_url": {
|
||||
"expression": "concat(\"https://www.moonweb.org/code\", http.request.uri.path)"
|
||||
},
|
||||
"status_code": 301,
|
||||
"preserve_query_string": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"expression": "http.host eq \"retro.moonweb.org\"",
|
||||
"description": "retro.moonweb.org -> www.moonweb.org/retro/",
|
||||
"action": "redirect",
|
||||
"action_parameters": {
|
||||
"from_value": {
|
||||
"target_url": {
|
||||
"expression": "concat(\"https://www.moonweb.org/retro\", http.request.uri.path)"
|
||||
},
|
||||
"status_code": 301,
|
||||
"preserve_query_string": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
Executable
+172
@@ -0,0 +1,172 @@
|
||||
#!/bin/bash
|
||||
# Setup Cloudflare redirect rules for moonweb.org
|
||||
# Redirects old subdomains to www.moonweb.org subdirectories
|
||||
#
|
||||
# Required env vars:
|
||||
# CLOUDFLARE_API_TOKEN - API token with Dynamic Redirects Write permission
|
||||
# CLOUDFLARE_ZONE_ID - Zone ID for moonweb.org
|
||||
#
|
||||
# Usage:
|
||||
# ./setup-redirects.sh # Apply redirects
|
||||
# ./setup-redirects.sh --dry-run # Show what would be created
|
||||
# ./setup-redirects.sh --status # Show current redirect rules
|
||||
# ./setup-redirects.sh --delete # Delete all redirect rules
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
RULES_FILE="$SCRIPT_DIR/redirect-rules.json"
|
||||
API="https://api.cloudflare.com/client/v4"
|
||||
PHASE="http_request_dynamic_redirect"
|
||||
RULESET_NAME="moonweb-subdomain-redirects"
|
||||
|
||||
DRY_RUN=false
|
||||
STATUS=false
|
||||
DELETE=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
--status) STATUS=true ;;
|
||||
--delete) DELETE=true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "${CLOUDFLARE_API_TOKEN:-}" ] || [ -z "${CLOUDFLARE_ZONE_ID:-}" ]; then
|
||||
echo "Error: CLOUDFLARE_API_TOKEN and CLOUDFLARE_ZONE_ID must be set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cf_api() {
|
||||
local method=$1
|
||||
local url=$2
|
||||
shift 2
|
||||
curl -s -X "$method" "$url" \
|
||||
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$@"
|
||||
}
|
||||
|
||||
# Get existing ruleset ID for this phase
|
||||
get_ruleset_id() {
|
||||
cf_api GET "$API/zones/$CLOUDFLARE_ZONE_ID/rulesets" | \
|
||||
python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
for r in data.get('result', []):
|
||||
if r.get('phase') == '$PHASE':
|
||||
print(r['id'])
|
||||
sys.exit(0)
|
||||
print('')
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
# Get rules in a ruleset
|
||||
get_rules() {
|
||||
local ruleset_id=$1
|
||||
cf_api GET "$API/zones/$CLOUDFLARE_ZONE_ID/rulesets/$ruleset_id" | \
|
||||
python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
for r in data.get('result', {}).get('rules', []):
|
||||
desc = r.get('description', 'unnamed')
|
||||
expr = r.get('expression', '')
|
||||
print(f' {desc}')
|
||||
print(f' expression: {expr}')
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
# Show current status
|
||||
if [ "$STATUS" = true ]; then
|
||||
echo "=== Current redirect rules ==="
|
||||
ruleset_id=$(get_ruleset_id)
|
||||
if [ -z "$ruleset_id" ]; then
|
||||
echo "No redirect ruleset found for phase $PHASE"
|
||||
exit 0
|
||||
fi
|
||||
echo "Ruleset ID: $ruleset_id"
|
||||
get_rules "$ruleset_id"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Delete all redirect rules
|
||||
if [ "$DELETE" = true ]; then
|
||||
echo "=== Deleting redirect rules ==="
|
||||
ruleset_id=$(get_ruleset_id)
|
||||
if [ -z "$ruleset_id" ]; then
|
||||
echo "No redirect ruleset found, nothing to delete"
|
||||
exit 0
|
||||
fi
|
||||
echo "Deleting ruleset $ruleset_id..."
|
||||
RESULT=$(cf_api DELETE "$API/zones/$CLOUDFLARE_ZONE_ID/rulesets/$ruleset_id")
|
||||
echo "$RESULT" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
if data.get('success'):
|
||||
print('Deleted successfully')
|
||||
else:
|
||||
for e in data.get('errors', []):
|
||||
print(f\"Error: {e.get('message', '')}\")
|
||||
" 2>/dev/null
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Build payload from JSON file
|
||||
PAYLOAD=$(python3 -c "
|
||||
import json
|
||||
with open('$RULES_FILE') as f:
|
||||
rules = json.load(f)
|
||||
payload = {
|
||||
'name': '$RULESET_NAME',
|
||||
'kind': 'zone',
|
||||
'phase': '$PHASE',
|
||||
'rules': rules
|
||||
}
|
||||
print(json.dumps(payload))
|
||||
")
|
||||
|
||||
RULE_COUNT=$(echo "$PAYLOAD" | python3 -c "import sys, json; print(len(json.load(sys.stdin)['rules']))" 2>/dev/null)
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
echo "=== DRY RUN ==="
|
||||
echo "Would create/update ruleset '$RULESET_NAME' with $RULE_COUNT rules:"
|
||||
python3 -c "
|
||||
import json
|
||||
with open('$RULES_FILE') as f:
|
||||
rules = json.load(f)
|
||||
for r in rules:
|
||||
print(f\" - {r['description']}\")
|
||||
" 2>/dev/null
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if ruleset already exists
|
||||
echo "=== Checking existing ruleset ==="
|
||||
EXISTING_ID=$(get_ruleset_id)
|
||||
|
||||
if [ -n "$EXISTING_ID" ]; then
|
||||
echo "Updating existing ruleset $EXISTING_ID..."
|
||||
RESULT=$(cf_api PUT "$API/zones/$CLOUDFLARE_ZONE_ID/rulesets/$EXISTING_ID" \
|
||||
-d "$PAYLOAD")
|
||||
else
|
||||
echo "Creating new ruleset..."
|
||||
RESULT=$(cf_api POST "$API/zones/$CLOUDFLARE_ZONE_ID/rulesets" \
|
||||
-d "$PAYLOAD")
|
||||
fi
|
||||
|
||||
# Check result
|
||||
echo "$RESULT" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
if data.get('success'):
|
||||
ruleset = data['result']
|
||||
print(f\"Success! Ruleset: {ruleset['name']} (ID: {ruleset['id']})\")
|
||||
print(f\"Rules: {len(ruleset.get('rules', []))}\")
|
||||
for r in ruleset.get('rules', []):
|
||||
print(f\" - {r.get('description', 'unnamed')}\")
|
||||
else:
|
||||
print('Error:')
|
||||
for e in data.get('errors', []):
|
||||
print(f\" {e.get('code', '')}: {e.get('message', '')}\")
|
||||
sys.exit(1)
|
||||
" 2>/dev/null
|
||||
@@ -14,7 +14,7 @@ import urllib.request
|
||||
GITHUB_USER = "skoelle"
|
||||
API_ROOT = "https://api.github.com"
|
||||
OUTPUT_PATH = os.path.join(
|
||||
os.path.dirname(__file__), "..", "..", "code", "_data", "repos.json"
|
||||
os.path.dirname(__file__), "..", "..", "_data", "repos.json"
|
||||
)
|
||||
|
||||
|
||||
|
||||
+12
-12
@@ -17,12 +17,12 @@
|
||||
<meta name="twitter:title" content="{{ title }}{% if 'moonweb' not in title and 'stefankoelle' not in title %} · moonweb.org{% endif %}">
|
||||
<meta name="twitter:description" content="{{ description }}">
|
||||
{% if ogImage %}<meta name="twitter:image" content="{{ site.url }}{{ ogImage }}">{% endif %}
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
<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="icon" href="{% if section and section != 'hub' %}/{{ section }}/favicon.svg{% else %}/favicon.svg{% endif %}">
|
||||
<link rel="stylesheet" href="/shared-base.css">
|
||||
<link rel="stylesheet" href="/theme.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",
|
||||
@@ -45,8 +45,8 @@
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Person",
|
||||
"name": "Stefan Koelle",
|
||||
"url": "https://hub.moonweb.org",
|
||||
"image": "https://hub.moonweb.org/stefan-koelle-foto.jpg",
|
||||
"url": "https://www.moonweb.org",
|
||||
"image": "https://www.moonweb.org/stefan-koelle-foto.jpg",
|
||||
"jobTitle": "Senior Software Developer & Architect",
|
||||
"address": {
|
||||
"@type": "PostalAddress",
|
||||
@@ -66,14 +66,14 @@
|
||||
<body>
|
||||
<header class="site-header">
|
||||
<div class="site-banner">
|
||||
<span class="site-title">{{ section }}.moonweb.org</span>
|
||||
<span class="site-title">moonweb.org{% if section and section != 'hub' %}/{{ section }}{% endif %}</span>
|
||||
</div>
|
||||
<nav class="site-switcher">
|
||||
<a href="https://hub.moonweb.org">hub</a>
|
||||
<a href="https://infra.moonweb.org">infra</a>
|
||||
<a href="https://smarthome.moonweb.org">smarthome</a>
|
||||
<a href="https://code.moonweb.org">code</a>
|
||||
<a href="https://retro.moonweb.org">retro</a>
|
||||
<a href="/">home</a>
|
||||
<a href="/infra/">infra</a>
|
||||
<a href="/smarthome/">smarthome</a>
|
||||
<a href="/code/">code</a>
|
||||
<a href="/retro/">retro</a>
|
||||
<a href="https://stefankoelle.de">cv</a>
|
||||
</nav>
|
||||
</header>
|
||||
@@ -84,8 +84,8 @@
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<p>Part of the <a href="https://hub.moonweb.org">moonweb.org</a> homelab.</p>
|
||||
<p><a href="https://hub.moonweb.org/impressum/">Legal Notice & Privacy Policy</a></p>
|
||||
<p>Part of the <a href="/">moonweb.org</a> homelab.</p>
|
||||
<p><a href="/impressum/">Legal Notice & Privacy Policy</a></p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
:root { --accent: #3E5098; }
|
||||
@@ -1 +0,0 @@
|
||||
:root { --accent: #99333A; }
|
||||
@@ -1 +0,0 @@
|
||||
:root { --accent: #8a6d3b; }
|
||||
@@ -1 +0,0 @@
|
||||
:root { --accent: #1f8a8a; }
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
title: "Page Not Found"
|
||||
section: "smarthome"
|
||||
description: "The page you are looking for does not exist."
|
||||
layout: base.njk
|
||||
permalink: /404.html
|
||||
---
|
||||
<div class="error-page">
|
||||
<h1>404</h1>
|
||||
<p>The page you are looking for does not exist or has been moved.</p>
|
||||
<a class="back-link" href="/">← Back to smarthome</a>
|
||||
</div>
|
||||
@@ -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://smarthome.moonweb.org" });
|
||||
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
|
||||
---
|
||||
|
||||
+10
-9
@@ -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:
|
||||
@@ -8,19 +9,19 @@ sections:
|
||||
cards:
|
||||
- title: "Home dashboard"
|
||||
summary: "Lighting, music, and Tasmota device control shown on the home dashboard."
|
||||
href: "/home-dashboard-controls/"
|
||||
href: "/smarthome/home-dashboard-controls/"
|
||||
emoji: "🎛️"
|
||||
- title: "Tasmota & energy monitoring"
|
||||
summary: "Smart plugs and power monitoring across the flat, feeding daily usage stats."
|
||||
href: "/tasmota-energy/"
|
||||
href: "/smarthome/tasmota-energy/"
|
||||
emoji: "⚡"
|
||||
- title: "HomematicIP + MQTT"
|
||||
summary: "How HomematicIP room sensors are bridged into MQTT and stored in InfluxDB."
|
||||
href: "/homematic-mqtt/"
|
||||
href: "/smarthome/homematic-mqtt/"
|
||||
emoji: "📡"
|
||||
- title: "Balkon Pi"
|
||||
summary: "Balcony sensor, lighting, and audio automation unit."
|
||||
href: "/balkonpi/"
|
||||
href: "/smarthome/balkonpi/"
|
||||
emoji: "🌿"
|
||||
- heading: "Dashboards & Hardware"
|
||||
cards:
|
||||
@@ -38,7 +39,7 @@ sections:
|
||||
emoji: "🖥️"
|
||||
- title: "Air quality dashboard"
|
||||
summary: "Real-time temperature and humidity monitoring across all rooms with historical charts."
|
||||
href: "/air-quality/"
|
||||
href: "/smarthome/air-quality/"
|
||||
emoji: "🌡️"
|
||||
- heading: "Personal services"
|
||||
cards:
|
||||
@@ -62,19 +63,19 @@ sections:
|
||||
cards:
|
||||
- title: "AirPlay audio"
|
||||
summary: "Multi-room AirPlay receivers with software volume boost."
|
||||
href: "/airplay-audio/"
|
||||
href: "/smarthome/airplay-audio/"
|
||||
emoji: "🔊"
|
||||
- title: "OctoPrint"
|
||||
summary: "Remote 3D printer control and camera streaming."
|
||||
href: "/octoprint/"
|
||||
href: "/smarthome/octoprint/"
|
||||
emoji: "🖨️"
|
||||
- title: "YouTube archiving"
|
||||
summary: "TubeArchivist and TubeSync for self-hosted YouTube downloading on the Synology."
|
||||
href: "/tubearchivist/"
|
||||
href: "/smarthome/tubearchivist/"
|
||||
emoji: "📺"
|
||||
- title: "Kids RFID MP3 Player"
|
||||
summary: "Arduino-based audio player for kids using RFID cards, built as a custom alternative to Hoerbert."
|
||||
href: "/kids-rfid-player/"
|
||||
href: "/smarthome/kids-rfid-player/"
|
||||
emoji: "🎵"
|
||||
---
|
||||
<p class="site-intro">The heart of the homelab. Home Assistant, MQTT sensors, dashboards, and automation projects that make everyday life a little more interesting.</p>
|
||||
|
||||
@@ -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,4 +0,0 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://smarthome.moonweb.org/sitemap.xml
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
permalink: /sitemap.xml
|
||||
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
|
||||
---
|
||||
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cleanup() { kill $(jobs -p) 2>/dev/null; }
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "==> Starting moonweb dev servers..."
|
||||
echo " http://localhost:8081 (moonweb sites)"
|
||||
echo " http://localhost:8086 (stefankoelle.de)"
|
||||
echo " http://localhost:8087 (timecapsule)"
|
||||
echo ""
|
||||
|
||||
npx @11ty/eleventy --serve --port=8081 &
|
||||
npx @11ty/eleventy --config=stefankoelle/eleventy.config.js --serve --port=8086 &
|
||||
cd timecapsule && npx @11ty/eleventy --serve --port=8087 &
|
||||
|
||||
wait
|
||||
@@ -26,11 +26,11 @@
|
||||
<span class="site-title">{{ siteTitle | default(section + ".moonweb.org") }}</span>
|
||||
</div>
|
||||
<nav class="site-switcher">
|
||||
<a href="https://hub.moonweb.org">hub</a>
|
||||
<a href="https://infra.moonweb.org">infra</a>
|
||||
<a href="https://smarthome.moonweb.org">smarthome</a>
|
||||
<a href="https://code.moonweb.org">code</a>
|
||||
<a href="https://retro.moonweb.org">retro</a>
|
||||
<a href="https://www.moonweb.org/">home</a>
|
||||
<a href="https://www.moonweb.org/infra/">infra</a>
|
||||
<a href="https://www.moonweb.org/smarthome/">smarthome</a>
|
||||
<a href="https://www.moonweb.org/code/">code</a>
|
||||
<a href="https://www.moonweb.org/retro/">retro</a>
|
||||
<a href="https://stefankoelle.de">cv</a>
|
||||
</nav>
|
||||
</header>
|
||||
@@ -41,8 +41,8 @@
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<p>Part of the <a href="https://hub.moonweb.org">moonweb.org</a> homelab.</p>
|
||||
<p><a href="https://hub.moonweb.org/impressum/">Legal Notice & Privacy Policy</a></p>
|
||||
<p>Part of the <a href="https://www.moonweb.org/">moonweb.org</a> homelab.</p>
|
||||
<p><a href="https://www.moonweb.org/impressum/">Legal Notice & Privacy Policy</a></p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user