feat(family): deploy Single-Writer DuckDB backend, Mila parent cockpit & feeding analytics, and Moritz macro tracker
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: duckdb-backend
|
||||
namespace: family
|
||||
labels:
|
||||
app.kubernetes.io/name: duckdb-backend
|
||||
app.kubernetes.io/part-of: family-portal
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: duckdb-backend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: duckdb-backend
|
||||
app.kubernetes.io/part-of: family-portal
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: registry-haumdaucher-de
|
||||
initContainers:
|
||||
- name: git-init
|
||||
image: alpine/git:latest
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
set -e
|
||||
export HOME=/tmp
|
||||
cd /wiki
|
||||
if [ ! -d ".git" ]; then
|
||||
echo "Initializing wiki repository on dedicated PVC..."
|
||||
REPO_URL="https://${GIT_SYNC_USERNAME}:${GIT_SYNC_PASSWORD}@git.moritzgraf.de/moritz/knowledge-wiki.git"
|
||||
git config --global http.sslVerify false
|
||||
git config --global --add safe.directory /wiki
|
||||
git config --global --add safe.directory '*'
|
||||
git init
|
||||
git remote add origin "$REPO_URL" || git remote set-url origin "$REPO_URL"
|
||||
git fetch origin main
|
||||
git checkout -f -B main origin/main
|
||||
echo "Clone completed successfully."
|
||||
else
|
||||
echo "Repository already initialized on PVC, updating..."
|
||||
REPO_URL="https://${GIT_SYNC_USERNAME}:${GIT_SYNC_PASSWORD}@git.moritzgraf.de/moritz/knowledge-wiki.git"
|
||||
git config --global http.sslVerify false
|
||||
git config --global --add safe.directory /wiki
|
||||
git config --global --add safe.directory '*'
|
||||
git remote set-url origin "$REPO_URL"
|
||||
if [ -f .git/shallow ]; then
|
||||
echo "Unshallowing existing repository..."
|
||||
git fetch --unshallow origin main || git fetch origin main
|
||||
else
|
||||
git fetch origin main
|
||||
fi
|
||||
git pull --rebase --autostash origin main || true
|
||||
fi
|
||||
env:
|
||||
- name: GIT_SYNC_USERNAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: wiki-git-credentials
|
||||
key: git-sync-username
|
||||
- name: GIT_SYNC_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: wiki-git-credentials
|
||||
key: wiki-sync-token
|
||||
volumeMounts:
|
||||
- name: duckdb-wiki-data
|
||||
mountPath: /wiki
|
||||
containers:
|
||||
- name: duckdb-backend
|
||||
image: registry.haumdaucher.de/family/duckdb-backend:latest
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: DATA_DIR
|
||||
value: "/wiki/data"
|
||||
- name: DUCKDB_DATA_DIR
|
||||
value: "/wiki/data"
|
||||
- name: WIKI_DIR
|
||||
value: "/wiki"
|
||||
- name: GIT_REPO_DIR
|
||||
value: "/wiki"
|
||||
- name: GIT_REPO_URL
|
||||
value: "https://git.moritzgraf.de/moritz/knowledge-wiki.git"
|
||||
- name: GIT_REMOTE_URL
|
||||
value: "https://git.moritzgraf.de/moritz/knowledge-wiki.git"
|
||||
- name: GIT_SYNC_USERNAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: wiki-git-credentials
|
||||
key: git-sync-username
|
||||
- name: GIT_SYNC_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: wiki-git-credentials
|
||||
key: wiki-sync-token
|
||||
- name: GIT_SYNC_INTERVAL_SECONDS
|
||||
value: "60"
|
||||
- name: PORT
|
||||
value: "8000"
|
||||
- name: HOST
|
||||
value: "0.0.0.0"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8000
|
||||
volumeMounts:
|
||||
- name: duckdb-wiki-data
|
||||
mountPath: /wiki
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /duckdb-api/v1/healthz
|
||||
port: 8000
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /duckdb-api/v1/healthz
|
||||
port: 8000
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
volumes:
|
||||
- name: duckdb-wiki-data
|
||||
persistentVolumeClaim:
|
||||
claimName: duckdb-wiki-data
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: duckdb-backend
|
||||
namespace: family
|
||||
labels:
|
||||
app.kubernetes.io/name: duckdb-backend
|
||||
app.kubernetes.io/part-of: family-portal
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 8000
|
||||
targetPort: 8000
|
||||
name: http
|
||||
selector:
|
||||
app.kubernetes.io/name: duckdb-backend
|
||||
Binary file not shown.
@@ -0,0 +1,55 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: family-portal
|
||||
namespace: family
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: "traefik"
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
kubernetes.io/tls-acme: "true"
|
||||
traefik.ingress.kubernetes.io/router.middlewares: auth-authentik-forwardauth@kubernetescrd
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- family.haumdaucher.de
|
||||
secretName: family-haumdaucher-de-tls
|
||||
rules:
|
||||
- host: family.haumdaucher.de
|
||||
http:
|
||||
paths:
|
||||
- path: /duckdb-api
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: duckdb-backend
|
||||
port:
|
||||
number: 8000
|
||||
- path: /tracking/mila
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: mila
|
||||
port:
|
||||
number: 80
|
||||
- path: /tracking/moritz
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: moritz
|
||||
port:
|
||||
number: 80
|
||||
- path: /outpost.goauthentik.io
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: duckdb-backend
|
||||
port:
|
||||
number: 8000
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: mila
|
||||
port:
|
||||
number: 80
|
||||
@@ -0,0 +1,64 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mila
|
||||
namespace: family
|
||||
labels:
|
||||
app.kubernetes.io/name: mila
|
||||
app.kubernetes.io/part-of: family-portal
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: mila
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: mila
|
||||
app.kubernetes.io/part-of: family-portal
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: registry-haumdaucher-de
|
||||
containers:
|
||||
- name: mila
|
||||
image: registry.haumdaucher.de/family/mila:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /tracking/mila/
|
||||
port: 80
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /tracking/mila/
|
||||
port: 80
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 15
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mila
|
||||
namespace: family
|
||||
labels:
|
||||
app.kubernetes.io/name: mila
|
||||
app.kubernetes.io/part-of: family-portal
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
name: http
|
||||
selector:
|
||||
app.kubernetes.io/name: mila
|
||||
@@ -0,0 +1,64 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: moritz
|
||||
namespace: family
|
||||
labels:
|
||||
app.kubernetes.io/name: moritz
|
||||
app.kubernetes.io/part-of: family-portal
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: moritz
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: moritz
|
||||
app.kubernetes.io/part-of: family-portal
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: registry-haumdaucher-de
|
||||
containers:
|
||||
- name: moritz
|
||||
image: registry.haumdaucher.de/family/moritz:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /tracking/moritz/
|
||||
port: 80
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /tracking/moritz/
|
||||
port: 80
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 15
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: moritz
|
||||
namespace: family
|
||||
labels:
|
||||
app.kubernetes.io/name: moritz
|
||||
app.kubernetes.io/part-of: family-portal
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
name: http
|
||||
selector:
|
||||
app.kubernetes.io/name: moritz
|
||||
@@ -0,0 +1,6 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: family
|
||||
labels:
|
||||
name: family
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: duckdb-wiki-data
|
||||
namespace: family
|
||||
labels:
|
||||
app.kubernetes.io/name: duckdb-backend
|
||||
app.kubernetes.io/part-of: family-portal
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: openebs-hostpath
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
Binary file not shown.
@@ -10,15 +10,37 @@ spec:
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: hosting-legacy-redirect
|
||||
name: hosting-redirect-mila
|
||||
namespace: minio
|
||||
spec:
|
||||
redirectRegex:
|
||||
regex: "^https?://([^/]+)/(mila|moritz|nutrition|sleep)\\.html(.*)"
|
||||
replacement: "https://${1}/family/${2}.html${3}"
|
||||
permanent: false
|
||||
regex: "^https?://([^/]+)/(?:family/)?(mila|sleep)\\.html.*"
|
||||
replacement: "https://family.haumdaucher.de/tracking/mila/"
|
||||
permanent: true
|
||||
---
|
||||
# Public Ingress for hosting.haumdaucher.de (Recipes, Public Assets)
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: hosting-redirect-moritz
|
||||
namespace: minio
|
||||
spec:
|
||||
redirectRegex:
|
||||
regex: "^https?://([^/]+)/(?:family/)?(moritz|nutrition)\\.html.*"
|
||||
replacement: "https://family.haumdaucher.de/tracking/moritz/"
|
||||
permanent: true
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: hosting-redirect-family-root
|
||||
namespace: minio
|
||||
spec:
|
||||
redirectRegex:
|
||||
regex: "^https?://([^/]+)/family/?$"
|
||||
replacement: "https://family.haumdaucher.de/"
|
||||
permanent: true
|
||||
---
|
||||
# Public Ingress for hosting.haumdaucher.de (Recipes, Public Assets, Legacy Redirects)
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
@@ -29,7 +51,7 @@ metadata:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
kubernetes.io/tls-acme: "true"
|
||||
traefik.ingress.kubernetes.io/router.priority: "10"
|
||||
traefik.ingress.kubernetes.io/router.middlewares: minio-hosting-legacy-redirect@kubernetescrd,minio-hosting-add-prefix@kubernetescrd
|
||||
traefik.ingress.kubernetes.io/router.middlewares: minio-hosting-redirect-mila@kubernetescrd,minio-hosting-redirect-moritz@kubernetescrd,minio-hosting-redirect-family-root@kubernetescrd,minio-hosting-add-prefix@kubernetescrd
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
@@ -48,8 +70,7 @@ spec:
|
||||
- hosting.haumdaucher.de
|
||||
secretName: hosting-haumdaucher-de-tls
|
||||
---
|
||||
# Protected Ingress for /family (Family Dashboards, Nutrition & Sleep Analytics)
|
||||
# Protected by Authentik ForwardAuth
|
||||
# Protected Ingress for /family (Redirects and Authentik Outpost)
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
@@ -60,7 +81,7 @@ metadata:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
kubernetes.io/tls-acme: "true"
|
||||
traefik.ingress.kubernetes.io/router.priority: "100"
|
||||
traefik.ingress.kubernetes.io/router.middlewares: auth-authentik-forwardauth@kubernetescrd,minio-hosting-add-prefix@kubernetescrd
|
||||
traefik.ingress.kubernetes.io/router.middlewares: minio-hosting-redirect-mila@kubernetescrd,minio-hosting-redirect-moritz@kubernetescrd,minio-hosting-redirect-family-root@kubernetescrd,auth-authentik-forwardauth@kubernetescrd,minio-hosting-add-prefix@kubernetescrd
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
|
||||
Binary file not shown.
+2
-19
@@ -25,18 +25,7 @@ jobs:
|
||||
delivery: "none"
|
||||
command: "python3 /home/node/.openclaw/scripts/compact_sessions.py"
|
||||
|
||||
# 2. Family Dashboards & Web Analytics
|
||||
- name: "dashboards-periodic-publish"
|
||||
declarationKey: "dashboards-periodic-publish"
|
||||
description: "Periodically regenerates and uploads Mila & Moritz family HTML dashboards to S3"
|
||||
schedule:
|
||||
kind: "every"
|
||||
every: "10m"
|
||||
session: "isolated"
|
||||
delivery: "none"
|
||||
command: "sh -lc 'export PATH=/home/node/.openclaw/bin:$PATH; cd /home/node/mop-skills/mop-skills/skills/datastore && uv run scripts/update_website.py --data-dir /home/node/.openclaw/wiki/main/data && uv run scripts/update_moritz_website.py --data-dir /home/node/.openclaw/wiki/main/data'"
|
||||
|
||||
# 3. Nightly Garmin Connect Mirror
|
||||
# 2. Nightly Garmin Connect Mirror
|
||||
- name: "garmin-nightly-mirror"
|
||||
declarationKey: "garmin-nightly-mirror"
|
||||
description: "Nächtlicher deterministischer Spiegel der Garmin Connect Daten (Aktivitäten, Daily Summary, Schlaf) in Git JSONL"
|
||||
@@ -56,13 +45,7 @@ jobs:
|
||||
Vorgehen:
|
||||
1. Führe den Garmin-Sync-Befehl aus:
|
||||
uv run /home/node/mop-skills/mop-skills/skills/datastore/scripts/garmin_sync.py --days 2 --data-dir /home/node/.openclaw/wiki/main/data
|
||||
2. Aktualisiere die gerenderten Markdown-Ansichten:
|
||||
PYTHONPATH=/home/node/mop-skills/mop-skills/skills/datastore/scripts uv run /home/node/mop-skills/mop-skills/skills/datastore/scripts/render_views.py --data-dir /home/node/.openclaw/wiki/main/data --wiki-dir /home/node/.openclaw/wiki/main
|
||||
3. Aktualisiere das persönliche Health & Nutrition Dashboard (moritz.html):
|
||||
uv run /home/node/mop-skills/mop-skills/skills/datastore/scripts/update_moritz_website.py --data-dir /home/node/.openclaw/wiki/main/data
|
||||
4. Git Commit & Push im Knowledge Wiki:
|
||||
cd /home/node/.openclaw/wiki/main && git add data/garmin/ entities/nutrition-log/ public/ && git diff --staged --quiet || (git commit -m "chore(garmin): automated nightly mirror [skip ci]" && git push origin main)
|
||||
5. Finale Antwort:
|
||||
2. Finale Antwort:
|
||||
- Bei Erfolg: Gib exakt 'NO_REPLY' aus (intentional silence, kein Telegram-Spam in der Nacht).
|
||||
- Nur bei Authentifizierungsfehlern: Informiere kurz via Telegram (Thema #Sport), dass ein Garmin-Re-Auth nötig ist, oder starte den Re-Auth-Flow.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user