Split Artcraft into independent chart applications

This commit is contained in:
2026-10-18 19:42:10 +02:00
parent a6282b60d0
commit a398a3a2ee
10 changed files with 14 additions and 423 deletions
-4
View File
@@ -1,4 +0,0 @@
apiVersion: v2
name: artcraft-homelab
type: application
version: 0.1.0
-45
View File
@@ -1,45 +0,0 @@
"""Download and unpack an official, versioned upstream web bundle."""
import os
from pathlib import Path
import shutil
import tempfile
import time
import urllib.request
import zipfile
url = os.environ["RELEASE_URL"]
if not url.startswith("https://github.com/storytold/"):
raise ValueError("Expected an official storytold GitHub release URL")
with tempfile.TemporaryDirectory() as tmp:
archive = Path(tmp) / "release.zip"
for attempt in range(4):
try:
with urllib.request.urlopen(url, timeout=120) as response, archive.open("wb") as out:
shutil.copyfileobj(response, out)
break
except Exception:
if attempt == 3:
raise
time.sleep(2 ** attempt)
unpacked = Path(tmp) / "unpacked"
with zipfile.ZipFile(archive) as bundle:
for member in bundle.infolist():
path = Path(member.filename)
if path.is_absolute() or ".." in path.parts or (member.external_attr >> 16) & 0o170000 == 0o120000:
raise ValueError(f"Unsafe archive entry: {path}")
bundle.extractall(unpacked)
roots = [p.parent for p in unpacked.glob("*/index.html")]
if (unpacked / "index.html").exists():
roots = [unpacked]
if len(roots) != 1:
raise ValueError("Expected exactly one web root")
if not any(roots[0].rglob("*.wasm")) and not any(roots[0].rglob("*.part-*")):
raise ValueError("Missing WebAssembly payload")
# Kubernetes owns the emptyDir root; do not try to chmod that directory.
for source in roots[0].iterdir():
target = Path("/srv/web") / source.name
if source.is_dir():
shutil.copytree(source, target, dirs_exist_ok=True)
else:
shutil.copy2(source, target)
print(f"Installed {url}", flush=True)
-37
View File
@@ -1,37 +0,0 @@
worker_processes auto;
pid /tmp/nginx.pid;
error_log /dev/stderr warn;
events { worker_connections 1024; }
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /dev/stdout;
client_body_temp_path /tmp/client_temp;
proxy_temp_path /tmp/proxy_temp;
fastcgi_temp_path /tmp/fastcgi_temp;
uwsgi_temp_path /tmp/uwsgi_temp;
scgi_temp_path /tmp/scgi_temp;
sendfile on;
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types application/wasm application/javascript text/javascript application/octet-stream text/css application/json;
server {
listen 8080;
root /srv/web;
index index.html;
add_header X-Content-Type-Options nosniff always;
# Some upstream apps use stable filenames. Revalidation prevents stale builds.
add_header Cache-Control no-cache always;
location / { try_files $uri $uri/ =404; }
location ~ /\. { deny all; }
location = /_headers { deny all; }
location = /nginx_status { return 404; }
}
server {
listen 127.0.0.1:8081;
access_log off;
location = /nginx_status { stub_status; }
location / { return 404; }
}
}
-243
View File
@@ -1,243 +0,0 @@
{{- range .Values.apps }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .name }}
namespace: {{ $.Release.Namespace }}
labels:
app.kubernetes.io/name: {{ .name }}
app.kubernetes.io/part-of: artcraft
spec:
replicas: 1
revisionHistoryLimit: 3
progressDeadlineSeconds: 900
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app.kubernetes.io/name: {{ .name }}
app.kubernetes.io/part-of: artcraft
template:
metadata:
labels:
app.kubernetes.io/name: {{ .name }}
app.kubernetes.io/part-of: artcraft
annotations:
artcraft.brunner.ninja/version: {{ .version | quote }}
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 101
runAsGroup: 101
fsGroup: 101
seccompProfile:
type: RuntimeDefault
initContainers:
- name: download-release
image: {{ $.Values.images.downloader }}
command: [python, /config/download.py]
env:
- name: RELEASE_URL
value: {{ .releaseUrl | quote }}
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]
resources:
requests:
cpu: 25m
memory: 64Mi
limits:
cpu: "1"
memory: 512Mi
volumeMounts:
- name: web
mountPath: /srv/web
- name: config
mountPath: /config
readOnly: true
- name: tmp
mountPath: /tmp
containers:
- name: nginx
image: {{ $.Values.images.nginx }}
command: [nginx, -g, "daemon off;", -c, /config/nginx.conf]
ports:
- name: http
containerPort: 8080
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 500m
memory: 128Mi
startupProbe:
httpGet:
path: /index.html
port: http
periodSeconds: 2
timeoutSeconds: 2
failureThreshold: 30
readinessProbe:
httpGet:
path: /index.html
port: http
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
livenessProbe:
httpGet:
path: /index.html
port: http
periodSeconds: 15
timeoutSeconds: 2
failureThreshold: 3
volumeMounts:
- name: web
mountPath: /srv/web
readOnly: true
- name: config
mountPath: /config
readOnly: true
- name: tmp
mountPath: /tmp
- name: nginx-exporter
image: {{ $.Values.images.exporter }}
args:
- --nginx.scrape-uri=http://127.0.0.1:8081/nginx_status
ports:
- name: metrics
containerPort: 9113
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]
resources:
requests:
cpu: 5m
memory: 16Mi
limits:
cpu: 100m
memory: 64Mi
startupProbe:
httpGet:
path: /metrics
port: metrics
periodSeconds: 2
timeoutSeconds: 2
failureThreshold: 30
readinessProbe:
httpGet:
path: /metrics
port: metrics
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
livenessProbe:
httpGet:
path: /metrics
port: metrics
periodSeconds: 20
timeoutSeconds: 2
failureThreshold: 3
volumes:
- name: web
emptyDir:
sizeLimit: 1Gi
- name: tmp
emptyDir:
sizeLimit: 1Gi
- name: config
configMap:
name: {{ $.Values.configMapName }}
nodeSelector:
odroid-h3: enabled
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
nodeTaintsPolicy: Honor
labelSelector:
matchLabels:
app.kubernetes.io/part-of: artcraft
---
apiVersion: v1
kind: Service
metadata:
name: {{ .name }}
namespace: {{ $.Release.Namespace }}
labels:
app.kubernetes.io/name: {{ .name }}
app.kubernetes.io/part-of: artcraft
spec:
selector:
app.kubernetes.io/name: {{ .name }}
app.kubernetes.io/part-of: artcraft
ports:
- name: http
port: 80
targetPort: http
- name: metrics
port: 9113
targetPort: metrics
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .name }}
namespace: {{ $.Release.Namespace }}
labels:
app.kubernetes.io/name: {{ .name }}
app.kubernetes.io/part-of: artcraft
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
ingressClassName: traefik
tls:
- hosts:
- {{ .host }}
rules:
- host: {{ .host }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ .name }}
port:
name: http
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ .name }}
namespace: {{ $.Release.Namespace }}
labels:
app.kubernetes.io/name: {{ .name }}
app.kubernetes.io/part-of: artcraft
spec:
selector:
matchLabels:
app.kubernetes.io/name: {{ .name }}
app.kubernetes.io/part-of: artcraft
endpoints:
- port: metrics
path: /metrics
interval: 30s
scrapeTimeout: 10s
---
{{- end }}
-10
View File
@@ -1,10 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Values.configMapName }}
namespace: {{ .Release.Namespace }}
data:
nginx.conf: |
{{ .Files.Get "files/nginx.conf" | indent 4 }}
download.py: |
{{ .Files.Get "files/download.py" | indent 4 }}
-4
View File
@@ -1,4 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: {{ .Release.Namespace }}
-58
View File
@@ -1,58 +0,0 @@
configMapName: artcraft-config-4h2gh76d48
images:
downloader: docker.io/library/python:3.13-alpine
nginx: docker.io/library/nginx:1.30.6-alpine
exporter: docker.io/nginx/nginx-prometheus-exporter:1.5.3
apps:
- name: cadcraft
version: v0.7.0
releaseUrl: https://github.com/storytold/cadcraft/releases/download/v0.7.0/cadcraft-web-0.7.0.zip
host: autocad.brunner.ninja
- name: deckcraft
version: v0.7.0
releaseUrl: https://github.com/storytold/deckcraft/releases/download/v0.7.0/deckcraft-web-0.7.0.zip
host: powerpoint.brunner.ninja
- name: designcraft
version: v0.8.0
releaseUrl: https://github.com/storytold/designcraft/releases/download/v0.8.0/designcraft-web-0.8.0.zip
host: indesign.brunner.ninja
- name: effectcraft
version: v0.10.0
releaseUrl: https://github.com/storytold/effectcraft/releases/download/v0.10.0/effectcraft-web-0.10.0.zip
host: aftereffects.brunner.ninja
- name: figcraft
version: v0.6.0
releaseUrl: https://github.com/storytold/figcraft/releases/download/v0.6.0/figcraft-web-0.6.0.zip
host: figma.brunner.ninja
- name: filmcraft
version: v0.8.0
releaseUrl: https://github.com/storytold/filmcraft/releases/download/v0.8.0/filmcraft-web-0.8.0.zip
host: premiere.brunner.ninja
- name: gridcraft
version: v0.7.0
releaseUrl: https://github.com/storytold/gridcraft/releases/download/v0.7.0/gridcraft-web-0.7.0.zip
host: excel.brunner.ninja
- name: lightcraft
version: v0.8.0
releaseUrl: https://github.com/storytold/lightcraft/releases/download/v0.8.0/lightcraft-web-0.8.0.zip
host: lightroom.brunner.ninja
- name: pdfcraft
version: v0.8.0
releaseUrl: https://github.com/storytold/pdfcraft/releases/download/v0.8.0/pdfcraft-web-0.8.0.zip
host: acrobat.brunner.ninja
- name: photocraft
version: v0.9.0
releaseUrl: https://github.com/storytold/photocraft/releases/download/v0.9.0/photocraft-web-0.9.0.zip
host: photoshop.brunner.ninja
- name: soundcraft
version: v0.7.0
releaseUrl: https://github.com/storytold/soundcraft/releases/download/v0.7.0/soundcraft-web-0.7.0.zip
host: protools.brunner.ninja
- name: vectorcraft
version: v0.12.0
releaseUrl: https://github.com/storytold/vectorcraft/releases/download/v0.12.0/vectorcraft-web-0.12.0.zip
host: illustrator.brunner.ninja
- name: wordcraft
version: v0.7.0
releaseUrl: https://github.com/storytold/wordcraft/releases/download/v0.7.0/wordcraft-web-0.7.0.zip
host: word.brunner.ninja
+1
View File
@@ -7,6 +7,7 @@ spec:
description: Stateless Artcraft web applications
sourceRepos:
- https://code.brunner.ninja/feedc0de/homelab-gitops.git
- https://brunner.ninja/charts
destinations:
- namespace: artcraft
server: https://kubernetes.default.svc
-21
View File
@@ -1,21 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: artcraft
namespace: argocd
spec:
project: artcraft
source:
repoURL: https://code.brunner.ninja/feedc0de/homelab-gitops.git
targetRevision: main
path: apps/artcraft
helm:
releaseName: artcraft
destination:
server: https://kubernetes.default.svc
namespace: artcraft
syncPolicy:
automated:
enabled: true
prune: true
selfHeal: true
+13 -1
View File
@@ -17,7 +17,19 @@ resources:
- watschn-studio.yaml
- artcraft-project.yaml
- coturn-project.yaml
- artcraft.yaml
- cadcraft.yaml
- deckcraft.yaml
- designcraft.yaml
- effectcraft.yaml
- figcraft.yaml
- filmcraft.yaml
- gridcraft.yaml
- lightcraft.yaml
- pdfcraft.yaml
- photocraft.yaml
- soundcraft.yaml
- vectorcraft.yaml
- wordcraft.yaml
- brunner-ninja.yaml
- coturn.yaml
- flaresolverr.yaml