diff --git a/zim/demo-library/dead-kiwix.png b/zim/demo-library/dead-kiwix.png
new file mode 100644
index 0000000..fc0beeb
Binary files /dev/null and b/zim/demo-library/dead-kiwix.png differ
diff --git a/zim/demo-library/dead.html b/zim/demo-library/dead.html
new file mode 100644
index 0000000..14250e8
--- /dev/null
+++ b/zim/demo-library/dead.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
There is nothing here, your URL is probably wrong.
+
+
+
diff --git a/zim/demo-library/demo-generator.cronjob.yaml b/zim/demo-library/demo-generator.cronjob.yaml
new file mode 100644
index 0000000..6e38ee5
--- /dev/null
+++ b/zim/demo-library/demo-generator.cronjob.yaml
@@ -0,0 +1,51 @@
+apiVersion: batch/v1
+kind: CronJob
+metadata:
+ name: demo-generator
+ namespace: zim
+spec:
+ schedule: "0 * * * *"
+ successfulJobsHistoryLimit: 1
+ failedJobsHistoryLimit: 1
+ concurrencyPolicy: Forbid
+ jobTemplate:
+ spec:
+ backoffLimit: 1
+ template:
+ spec:
+ containers:
+ - name: debian
+ image: ghcr.io/kiwix/maintenance:latest
+ imagePullPolicy: Always
+ env:
+ - name: INSTALL_SCRIPTS
+ value: "demo-generator#github://kiwix/k8s/zim/library-mgmt/demo-generator.py\n"
+ volumeMounts:
+ - mountPath: "/data/demo"
+ subPath: demo
+ name: library-volume
+ - mountPath: "/data/zim"
+ subPath: zim
+ name: download-volume
+ readOnly: true
+ - mountPath: "/data/hidden-zim"
+ name: hidden-volume
+ readOnly: true
+ args: ["demo-generator"]
+ resources:
+ limits:
+ cpu: 200m
+ memory: 256Mi
+ volumes:
+ - name: library-volume
+ persistentVolumeClaim:
+ claimName: kiwix-library-pvc
+ - name: download-volume
+ persistentVolumeClaim:
+ claimName: kiwix-download-pvc
+ - name: hidden-volume
+ persistentVolumeClaim:
+ claimName: hidden-zim-pvc
+ restartPolicy: Never
+ nodeSelector:
+ k8s.kiwix.org/role: "storage"
diff --git a/zim/demo-library/demo-generator.py b/zim/demo-library/demo-generator.py
new file mode 100644
index 0000000..84f9cb3
--- /dev/null
+++ b/zim/demo-library/demo-generator.py
@@ -0,0 +1,383 @@
+#!/usr/bin/env python3
+
+import datetime
+import re
+import shutil
+import tempfile
+from contextlib import contextmanager
+from pathlib import Path
+from typing import Any
+
+import requests
+import yaml
+from humanfriendly import format_size as human_size
+from jinja2 import Environment, FileSystemLoader, Template, select_autoescape
+from lxml import etree
+from pydantic import BaseModel
+from zimscraperlib.logging import getLogger # pyright: ignore [reportMissingTypeStubs]
+from zimscraperlib.zim import Archive # pyright: ignore [reportMissingTypeStubs]
+
+# in-ZIM metadata to in-library XML attributes
+NAMES_MAP: dict[str, str] = {
+ "Title": "title",
+ "Description": "description",
+ "Language": "language",
+ "Creator": "creator",
+ "Publisher": "publisher",
+ "Name": "name",
+ "Flavour": "flavour",
+ "Tags": "tags",
+ "Date": "date",
+}
+
+COPIED_KEYS = ["id", "size", "url", "mediaCount", "articleCount", "path"] + list(
+ NAMES_MAP.values()
+)
+
+
+FILENAME_FMT = re.compile(
+ r"^(?P