Compare commits
68 Commits
c94959a687
...
ea08411128
| Author | SHA1 | Date | |
|---|---|---|---|
| ea08411128 | |||
| bff6b83d11 | |||
| a94bd95248 | |||
| 2c0622583e | |||
| 86490b74c4 | |||
| 2ef8a7bbc2 | |||
| ae85dcfeaa | |||
| 41292eff0b | |||
| a69bd45455 | |||
| a3a5b1a9bd | |||
| 938f6b336c | |||
| 3c97a02fa7 | |||
| 980892a5b4 | |||
| adf7d7eb31 | |||
| 2fe8f7ea6a | |||
| c00b760976 | |||
| d78fc77825 | |||
| a6ab2b44af | |||
| 3a207c7d94 | |||
| d45cf950ec | |||
| 193c820fc6 | |||
| c3524cec3d | |||
| f214e394d0 | |||
| 07cffbeec0 | |||
| 576221c47d | |||
| f63d39e5aa | |||
| 48bce52660 | |||
| 5b1a209d9a | |||
| 5437b985e8 | |||
| f49e341445 | |||
| 8c64a4b067 | |||
| 7b5001c581 | |||
| fc0c5c1250 | |||
| 39fc2aacde | |||
| 33f0d67b34 | |||
| 48a2a53023 | |||
| 269b6cd7ad | |||
| b06b5d7612 | |||
| 0f1994c384 | |||
| 3df06948a9 | |||
| 30ac7e5ac1 | |||
| 0b8e4f012a | |||
| 2eecba7f55 | |||
| bd5f1b3a67 | |||
| 9ff70673e3 | |||
| 755c54f26b | |||
| f4588b4304 | |||
| e36f7059ea | |||
| 6deefc514e | |||
| 33ff3d20aa | |||
| 65de7602c9 | |||
| 9b77a89b0d | |||
| 6a86590484 | |||
| 8cc80f695f | |||
| 50c25b1b92 | |||
| a85fac9002 | |||
| 5bfeffe31f | |||
| 8459ea7058 | |||
| 6efe79819f | |||
| 33d07dcf5c | |||
| 7257762c45 | |||
| bff64dba65 | |||
| f72dc43f76 | |||
| 47a73af27e | |||
| 1ee60d9534 | |||
| 63d82af268 | |||
| 47cbc9b9f6 | |||
| 001e9c36fe |
@ -8,6 +8,9 @@ parameters:
|
||||
fromBackup: ""
|
||||
numberOfReplicas: "2"
|
||||
staleReplicaTimeout: "30"
|
||||
fsType: "ext4"
|
||||
replicaAutoBalance: "least-effort"
|
||||
dataLocality: "disabled"
|
||||
provisioner: driver.longhorn.io
|
||||
reclaimPolicy: Retain
|
||||
allowVolumeExpansion: true
|
||||
|
||||
49
infrastructure/core/gpu/device-plugin-tethys/daemonset.yaml
Normal file
49
infrastructure/core/gpu/device-plugin-tethys/daemonset.yaml
Normal file
@ -0,0 +1,49 @@
|
||||
# infrastructure/core/gpu/daemonsets/device-plugin-tethys/daemonset.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: nvidia-device-plugin-tethys
|
||||
namespace: kube-system
|
||||
labels:
|
||||
app.kubernetes.io/name: nvidia-device-plugin
|
||||
app.kubernetes.io/instance: titan24
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: nvidia-device-plugin
|
||||
app.kubernetes.io/instance: titan24
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: nvidia-device-plugin
|
||||
app.kubernetes.io/instance: titan24
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: titan-24
|
||||
kubernetes.io/arch: amd64
|
||||
tolerations:
|
||||
- operator: Exists
|
||||
priorityClassName: system-node-critical
|
||||
runtimeClassName: nvidia
|
||||
containers:
|
||||
- name: nvidia-device-plugin-ctr
|
||||
image: nvcr.io/nvidia/k8s-device-plugin:v0.16.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- "--fail-on-init-error=false"
|
||||
- "--device-list-strategy=envvar"
|
||||
- "--mig-strategy=none"
|
||||
securityContext:
|
||||
privileged: true
|
||||
env:
|
||||
- name: NVIDIA_VISIBLE_DEVICES
|
||||
value: "all"
|
||||
- name: NVIDIA_DRIVER_CAPABILITIES
|
||||
value: "compute,video,utility"
|
||||
volumeMounts:
|
||||
- name: device-plugin
|
||||
mountPath: /var/lib/kubelet/device-plugins
|
||||
volumes:
|
||||
- name: device-plugin
|
||||
hostPath:
|
||||
path: /var/lib/kubelet/device-plugins
|
||||
@ -0,0 +1,5 @@
|
||||
# infrastructure/core/gpu/daemonsets/device-plugin-tethys/kustomization.yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- daemonset.yaml
|
||||
@ -0,0 +1,6 @@
|
||||
# infrastructure/core/gpu/daemonsets/profiles/jetson-and-tethys/kustomization.yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../device-plugin-jetson
|
||||
- ../../device-plugin-tethys
|
||||
@ -0,0 +1,7 @@
|
||||
# infrastructure/core/gpu/daemonsets/profiles/minipc-and-jetson-and-tethys/kustomization.yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../device-plugin-minipc
|
||||
- ../../device-plugin-tethys
|
||||
- ../../device-plugin-jetson
|
||||
@ -0,0 +1,6 @@
|
||||
# infrastructure/core/gpu/daemonsets/profiles/minipc-and-tethys/kustomization.yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../device-plugin-minipc
|
||||
- ../../device-plugin-tethys
|
||||
@ -0,0 +1,5 @@
|
||||
# infrastructure/core/gpu/daemonsets/profiles/tethys-only/kustomization.yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../device-plugin-tethys
|
||||
@ -0,0 +1,5 @@
|
||||
apiVersion: node.k8s.io/v1
|
||||
kind: RuntimeClass
|
||||
metadata:
|
||||
name: nvidia
|
||||
handler: nvidia
|
||||
@ -5,4 +5,5 @@ resources:
|
||||
- base
|
||||
# - gpu/profiles/jetson-only
|
||||
# - gpu/profiles/minipc-and-jetson
|
||||
- gpu/profiles/minipc-only
|
||||
# - gpu/profiles/minipc-only
|
||||
- gpu/profiles/tethys-only
|
||||
|
||||
19
infrastructure/flux-system/image-automation-pegasus.yaml
Normal file
19
infrastructure/flux-system/image-automation-pegasus.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
# infrastructure/flux-system/image-automation-pegasus.yaml
|
||||
apiVersion: image.toolkit.fluxcd.io/v1beta1
|
||||
kind: ImageUpdateAutomation
|
||||
metadata:
|
||||
name: pegasus
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 1m0s
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: flux-system
|
||||
git:
|
||||
commit:
|
||||
authorEmail: ops@bstein.dev
|
||||
authorName: flux-bot
|
||||
messageTemplate: "chore(pegasus): update image to {{range .Updated.Images}}{{.}}{{end}}"
|
||||
update:
|
||||
strategy: Setters
|
||||
path: ./services/pegasus
|
||||
19
infrastructure/flux-system/kustomization-jitsi.yaml
Normal file
19
infrastructure/flux-system/kustomization-jitsi.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
# infrastructure/flux-system/kustomization-jitsi.yaml
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
name: jitsi
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 10m
|
||||
path: ./services/jitsi
|
||||
targetNamespace: jitsi
|
||||
prune: true
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: flux-system
|
||||
namespace: flux-system
|
||||
dependsOn:
|
||||
- name: core
|
||||
wait: true
|
||||
timeout: 5m
|
||||
@ -1,3 +1,4 @@
|
||||
# infrastructure/flux-system/kustomization-longhorn-ui.yaml
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
|
||||
14
infrastructure/flux-system/kustomization-monitoring.yaml
Normal file
14
infrastructure/flux-system/kustomization-monitoring.yaml
Normal file
@ -0,0 +1,14 @@
|
||||
# infrastructure/flux-system/kustomization-monitoring.yaml
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
name: monitoring
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 10m
|
||||
path: ./services/monitoring
|
||||
prune: true
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: flux-system
|
||||
wait: true
|
||||
19
infrastructure/flux-system/kustomization-pegasus.yaml
Normal file
19
infrastructure/flux-system/kustomization-pegasus.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
# infrastructure/flux-system/kustomization-pegasus.yaml
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
name: pegasus
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 10m
|
||||
path: ./services/pegasus
|
||||
targetNamespace: jellyfin
|
||||
prune: true
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: flux-system
|
||||
namespace: flux-system
|
||||
dependsOn:
|
||||
- name: core
|
||||
wait: true
|
||||
timeout: 5m
|
||||
18
infrastructure/flux-system/kustomization-traefik.yaml
Normal file
18
infrastructure/flux-system/kustomization-traefik.yaml
Normal file
@ -0,0 +1,18 @@
|
||||
# infrastructure/flux-system/kustomization-traefik.yaml
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
name: traefik
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 10m
|
||||
path: ./infrastructure/traefik
|
||||
targetNamespace: traefik
|
||||
prune: true
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: flux-system
|
||||
namespace: flux-system
|
||||
dependsOn:
|
||||
- name: core
|
||||
wait: true
|
||||
@ -9,8 +9,12 @@ resources:
|
||||
- kustomization-helm.yaml
|
||||
- kustomization-gitea.yaml
|
||||
- kustomization-vault.yaml
|
||||
- kustomization-jitsi.yaml
|
||||
- kustomization-crypto.yaml
|
||||
- kustomization-traefik.yaml
|
||||
- kustomization-monerod.yaml
|
||||
- kustomization-pegasus.yaml
|
||||
- kustomization-jellyfin.yaml
|
||||
- kustomization-xmr-miner.yaml
|
||||
- kustomization-monitoring.yaml
|
||||
- kustomization-longhorn-ui.yaml
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
# infrastructure/sources/helm/grafana.yaml
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
# infrastructure/sources/helm/hashicorp.yaml
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
# infrastructure/sources/helm/jetstack.yaml
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
# infrastructure/sources/helm/prometheus.yaml
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
|
||||
9
infrastructure/sources/helm/victoria-metrics.yaml
Normal file
9
infrastructure/sources/helm/victoria-metrics.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
# infrastructure/sources/helm/victoria-metrics.yaml
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: victoria-metrics
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 1h
|
||||
url: https://victoriametrics.github.io/helm-charts/
|
||||
@ -35,6 +35,12 @@ items:
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
- --api.dashboard=true
|
||||
- --metrics.prometheus=true
|
||||
- --metrics.prometheus.addEntryPointsLabels=true
|
||||
- --metrics.prometheus.addRoutersLabels=true
|
||||
- --metrics.prometheus.addServicesLabels=true
|
||||
- --entrypoints.metrics.address=:9100
|
||||
- --metrics.prometheus.entryPoint=metrics
|
||||
image: traefik:v3.3.3
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: traefik
|
||||
@ -48,6 +54,9 @@ items:
|
||||
- containerPort: 8080
|
||||
name: admin
|
||||
protocol: TCP
|
||||
- containerPort: 9100
|
||||
name: metrics
|
||||
protocol: TCP
|
||||
terminationMessagePath: /dev/termination-log
|
||||
terminationMessagePolicy: File
|
||||
dnsPolicy: ClusterFirst
|
||||
|
||||
@ -9,3 +9,4 @@ resources:
|
||||
- serviceaccount.yaml
|
||||
- clusterrole.yaml
|
||||
- clusterrolebinding.yaml
|
||||
- service.yaml
|
||||
|
||||
20
infrastructure/traefik/service.yaml
Normal file
20
infrastructure/traefik/service.yaml
Normal file
@ -0,0 +1,20 @@
|
||||
# infrastructure/traefik/service.yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: traefik-metrics
|
||||
namespace: traefik
|
||||
labels:
|
||||
app: traefik
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "9100"
|
||||
prometheus.io/path: "/metrics"
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: traefik
|
||||
ports:
|
||||
- name: metrics
|
||||
port: 9100
|
||||
targetPort: metrics
|
||||
2
scripts/longhorn_volume_usage.fish
Normal file → Executable file
2
scripts/longhorn_volume_usage.fish
Normal file → Executable file
@ -1,3 +1,5 @@
|
||||
#!/usr/bin/env fish
|
||||
|
||||
function pvc-usage --description "Show Longhorn PVC usage (human-readable) mapped to namespace/name"
|
||||
begin
|
||||
kubectl -n longhorn-system get volumes.longhorn.io -o json \
|
||||
|
||||
218
scripts/styx_kioskification.sh
Normal file
218
scripts/styx_kioskification.sh
Normal file
@ -0,0 +1,218 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# 0) Create dedicated user if it doesn't exist
|
||||
if ! id -u styx >/dev/null 2>&1; then
|
||||
sudo useradd -m -s /bin/bash styx
|
||||
echo "Created user 'styx'"
|
||||
fi
|
||||
|
||||
# 1) App directory
|
||||
sudo mkdir -p /opt/styx-kiosk/keys
|
||||
sudo chown -R styx:styx /opt/styx-kiosk
|
||||
|
||||
# 2) Drop the kiosk app (written below) into place
|
||||
sudo tee /opt/styx-kiosk/kiosk.py >/dev/null <<'PY'
|
||||
#!/usr/bin/env python3
|
||||
import base64, json, os, subprocess, threading, tempfile
|
||||
from datetime import datetime
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox
|
||||
|
||||
APP_TITLE = "STYX Airgap Signer"
|
||||
CAMERA_DEV = os.environ.get("ZBAR_DEV", "/dev/video0")
|
||||
KEY_PATH = os.environ.get("STYX_KEY", "/vault/keys/signer_ed25519.pem") # in the LUKS vault
|
||||
ALGO = os.environ.get("STYX_ALGO", "ed25519") # or 'secp256r1'
|
||||
QR_TMP = "/tmp/styx_signed.png"
|
||||
|
||||
def zbar_scan_oneshot():
|
||||
# --raw -> data only; --nodisplay -> no preview window; --oneshot -> exit after first code
|
||||
# (zbarcam supports --oneshot; prints one code and exits). :contentReference[oaicite:2]{index=2}
|
||||
cmd = ["zbarcam", "--raw", "--nodisplay", "--oneshot", CAMERA_DEV]
|
||||
try:
|
||||
out = subprocess.check_output(cmd, text=True, timeout=30)
|
||||
out = out.strip()
|
||||
return out if out else None
|
||||
except Exception as e:
|
||||
return None
|
||||
|
||||
def openssl_pub_der_b64(key_path):
|
||||
der = subprocess.check_output(["openssl","pkey","-in",key_path,"-pubout","-outform","DER"])
|
||||
return base64.b64encode(der).decode()
|
||||
|
||||
def sign_bytes(msg: bytes, key_path: str, algo: str) -> bytes:
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
f.write(msg)
|
||||
msg_path = f.name
|
||||
try:
|
||||
if algo.lower() == "ed25519":
|
||||
# Ed25519 expects raw message; OpenSSL handles hashing internally.
|
||||
sig = subprocess.check_output(
|
||||
["openssl","pkeyutl","-sign","-inkey",key_path,"-rawin","-in",msg_path]
|
||||
)
|
||||
return sig
|
||||
elif algo.lower() in ("secp256r1","prime256v1","p256"):
|
||||
# ECDSA over P-256; hash with SHA-256; OpenSSL returns DER-encoded (r,s)
|
||||
sig = subprocess.check_output(
|
||||
["openssl","dgst","-sha256","-sign",key_path,msg_path]
|
||||
)
|
||||
return sig
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported algo: {algo}")
|
||||
finally:
|
||||
try: os.unlink(msg_path)
|
||||
except: pass
|
||||
|
||||
def make_signed_envelope(scanned_text: str, key_path: str, algo: str) -> dict:
|
||||
# Accept either raw string or JSON with 'tx_bytes' (base64) or 'message'
|
||||
try:
|
||||
obj = json.loads(scanned_text)
|
||||
if "tx_bytes" in obj:
|
||||
msg = base64.b64decode(obj["tx_bytes"])
|
||||
elif "message" in obj:
|
||||
msg = obj["message"].encode()
|
||||
else:
|
||||
# If it's JSON but doesn't carry known fields, sign canonical JSON bytes
|
||||
msg = json.dumps(obj, sort_keys=True, separators=(",",":")).encode()
|
||||
request_id = obj.get("request_id")
|
||||
except Exception:
|
||||
# Non-JSON → treat the scanned text as the message to sign
|
||||
msg = scanned_text.encode()
|
||||
request_id = None
|
||||
|
||||
sig = sign_bytes(msg, key_path, algo)
|
||||
env = {
|
||||
"algo": algo.lower(),
|
||||
"signature_b64": base64.b64encode(sig).decode(),
|
||||
"pubkey_spki_der_b64": openssl_pub_der_b64(key_path),
|
||||
"payload_sha256_b64": base64.b64encode(subprocess.check_output(["openssl","dgst","-sha256","-binary"], input=msg)).decode(),
|
||||
"quote_raw": scanned_text,
|
||||
"request_id": request_id,
|
||||
"device": os.uname().nodename,
|
||||
"ts_utc": datetime.utcnow().isoformat(timespec="seconds") + "Z",
|
||||
}
|
||||
return env
|
||||
|
||||
def qrencode_to_file(text: str, path: str):
|
||||
# Use qrencode CLI to render a PNG we can display.
|
||||
subprocess.run(["qrencode","-l","M","-s","16","-t","PNG","-o",path], input=text.encode(), check=True)
|
||||
|
||||
class App(tk.Tk):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.title(APP_TITLE)
|
||||
self.attributes("-fullscreen", True)
|
||||
self.configure(background="black")
|
||||
self.bind("<Escape>", lambda e: self.quit()) # for maintenance only
|
||||
|
||||
s = ttk.Style(self)
|
||||
s.configure("Big.TButton", font=("DejaVu Sans", 48), padding=24)
|
||||
s.configure("Big.TLabel", font=("DejaVu Sans", 32), foreground="white", background="black")
|
||||
|
||||
self.container = tk.Frame(self, bg="black")
|
||||
self.container.pack(expand=True, fill="both")
|
||||
|
||||
self.status = ttk.Label(self.container, text="Ready", style="Big.TLabel")
|
||||
self.status.pack(pady=20)
|
||||
|
||||
self.scan_btn = ttk.Button(self.container, text="SCAN", style="Big.TButton", command=self.start_scan)
|
||||
self.scan_btn.pack(pady=20)
|
||||
|
||||
self.image_label = tk.Label(self.container, bg="black")
|
||||
self.image_label.pack(pady=10)
|
||||
|
||||
self.new_btn = ttk.Button(self.container, text="NEW SCAN", style="Big.TButton", command=self.reset)
|
||||
self.new_btn.pack_forget()
|
||||
|
||||
self.note = ttk.Label(self.container, text="", style="Big.TLabel")
|
||||
self.note.pack(pady=10)
|
||||
|
||||
if not os.path.exists(KEY_PATH):
|
||||
self.status.config(text=f"Key not found at {KEY_PATH}\nInsert/unlock vault to proceed.")
|
||||
|
||||
def reset(self):
|
||||
self.image_label.configure(image="")
|
||||
self.image_label.image = None
|
||||
self.new_btn.pack_forget()
|
||||
self.note.config(text="")
|
||||
self.status.config(text="Ready")
|
||||
self.scan_btn.config(state="normal")
|
||||
|
||||
def start_scan(self):
|
||||
if not os.path.exists(KEY_PATH):
|
||||
messagebox.showerror("Key missing", f"Signing key not found at:\n{KEY_PATH}\nUnlock your vault.")
|
||||
return
|
||||
self.status.config(text="Scanning…")
|
||||
self.scan_btn.config(state="disabled")
|
||||
threading.Thread(target=self._do_scan_and_sign, daemon=True).start()
|
||||
|
||||
def _do_scan_and_sign(self):
|
||||
scanned = zbar_scan_oneshot()
|
||||
if not scanned:
|
||||
self.after(0, self._scan_failed)
|
||||
return
|
||||
try:
|
||||
envelope = make_signed_envelope(scanned, KEY_PATH, ALGO)
|
||||
payload = json.dumps(envelope, separators=(",",":"))
|
||||
qrencode_to_file(payload, QR_TMP)
|
||||
self.after(0, self._show_qr, envelope)
|
||||
except Exception as e:
|
||||
self.after(0, lambda: self._error(str(e)))
|
||||
|
||||
def _scan_failed(self):
|
||||
self.status.config(text="No QR detected. Try again.")
|
||||
self.scan_btn.config(state="normal")
|
||||
|
||||
def _show_qr(self, envelope):
|
||||
# Display the PNG produced by qrencode
|
||||
try:
|
||||
img = tk.PhotoImage(file=QR_TMP)
|
||||
self.image_label.configure(image=img)
|
||||
self.image_label.image = img
|
||||
except Exception as e:
|
||||
self.status.config(text=f"QR render failed: {e}")
|
||||
self.scan_btn.config(state="normal")
|
||||
return
|
||||
self.status.config(text="Signed. Show this QR to your online box.")
|
||||
self.note.config(text=f"Algo: {envelope['algo']} Host: {envelope['device']}")
|
||||
self.new_btn.pack(pady=20)
|
||||
|
||||
if __name__ == "__main__":
|
||||
App().mainloop()
|
||||
PY
|
||||
sudo chmod +x /opt/styx-kiosk/kiosk.py
|
||||
sudo chown -R styx:styx /opt/styx-kiosk
|
||||
|
||||
# 3) Minimal X session: openbox + kiosk; no mouse pointer
|
||||
sudo -u styx tee /home/styx/.xinitrc >/dev/null <<'XRC'
|
||||
xset -dpms
|
||||
xset s off
|
||||
xset s noblank
|
||||
# If 'unclutter' is installed, uncomment the next line to hide cursor:
|
||||
# unclutter -idle 0 -root &
|
||||
openbox-session &
|
||||
/opt/styx-kiosk/kiosk.py
|
||||
XRC
|
||||
sudo chown styx:styx /home/styx/.xinitrc
|
||||
sudo chmod 0755 /home/styx/.xinitrc
|
||||
|
||||
# 4) Autologin the 'styx' user on tty1, auto-start X
|
||||
sudo mkdir -p /etc/systemd/system/getty@tty1.service.d
|
||||
sudo tee /etc/systemd/system/getty@tty1.service.d/override.conf >/dev/null <<'OVR'
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=-/sbin/agetty --autologin styx --noclear %I $TERM
|
||||
Type=idle
|
||||
OVR
|
||||
|
||||
sudo -u styx tee -a /home/styx/.bash_profile >/dev/null <<'BRC'
|
||||
# Start X on the first tty automatically, headless
|
||||
if [ -z "$DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then
|
||||
exec startx -- -nocursor
|
||||
fi
|
||||
BRC
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable getty@tty1.service
|
||||
|
||||
echo "Done. Reboot to try the kiosk."
|
||||
195
scripts/styx_prep.sh
Executable file
195
scripts/styx_prep.sh
Executable file
@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# === CONFIG ===
|
||||
STYX_USER="styx"
|
||||
STYX_PASS="TempPass#123" # change at first login
|
||||
STYX_HOSTNAME="styx"
|
||||
SSH_PUBKEY="" # e.g., 'ssh-ed25519 AAAA... your@host' (optional)
|
||||
|
||||
# === helpers ===
|
||||
require_root() {
|
||||
if [[ $EUID -ne 0 ]]; then exec sudo -E "$0" "$@"; fi
|
||||
}
|
||||
|
||||
ensure_binfmt_arm64() {
|
||||
# If binfmt for arm64 isn't registered, register it via Docker (idempotent).
|
||||
if [[ ! -e /proc/sys/fs/binfmt_misc/qemu-aarch64 ]]; then
|
||||
command -v docker >/dev/null || { echo "Docker required to register binfmt (sudo pacman -S docker)"; exit 1; }
|
||||
sudo systemctl enable --now docker >/dev/null 2>&1 || true
|
||||
sudo docker run --rm --privileged tonistiigi/binfmt --install arm64
|
||||
fi
|
||||
}
|
||||
|
||||
find_parts() {
|
||||
BOOT=$(lsblk -o LABEL,PATH -nr | awk '$1=="system-boot"{print $2}' | head -n1)
|
||||
ROOT=$(lsblk -o LABEL,PATH -nr | awk '$1=="writable"{print $2}' | head -n1)
|
||||
if [[ -z "${BOOT:-}" || -z "${ROOT:-}" ]]; then
|
||||
echo "Could not find 'system-boot'/'writable' on any device."
|
||||
lsblk -o NAME,SIZE,FSTYPE,LABEL,PATH -nr
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
mount_parts() {
|
||||
mkdir -p /mnt/pi-boot /mnt/pi-root
|
||||
mount "$ROOT" /mnt/pi-root
|
||||
mount "$BOOT" /mnt/pi-boot
|
||||
|
||||
# Bind only what we need (avoid /run to prevent postinst fights)
|
||||
for d in dev dev/pts proc sys; do mount --bind "/$d" "/mnt/pi-root/$d"; done
|
||||
|
||||
# Ubuntu images use a resolv.conf symlink—replace with a real file
|
||||
if [[ -L /mnt/pi-root/etc/resolv.conf || ! -e /mnt/pi-root/etc/resolv.conf ]]; then
|
||||
rm -f /mnt/pi-root/etc/resolv.conf
|
||||
cat /etc/resolv.conf > /mnt/pi-root/etc/resolv.conf
|
||||
fi
|
||||
}
|
||||
|
||||
prep_chroot() {
|
||||
# Block service starts inside chroot (no systemd there)
|
||||
cat >/mnt/pi-root/usr/sbin/policy-rc.d <<'EOF'
|
||||
#!/bin/sh
|
||||
exit 101
|
||||
EOF
|
||||
chmod +x /mnt/pi-root/usr/sbin/policy-rc.d
|
||||
|
||||
# All the work happens inside the ARM64 rootfs
|
||||
CHCMD=$(cat <<'EOS'
|
||||
set -euo pipefail
|
||||
export DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC
|
||||
# Ensure sbin is in PATH so user/group tools work
|
||||
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
|
||||
apt-get update
|
||||
apt-get -y full-upgrade
|
||||
|
||||
# Remove snaps and keep them gone (Ubuntu for Pi ships with snaps)
|
||||
apt-get -y purge snapd || true
|
||||
rm -rf /snap /var/snap /var/lib/snapd /home/*/snap || true
|
||||
mkdir -p /etc/apt/preferences.d
|
||||
printf 'Package: snapd\nPin: release *\nPin-Priority: -10\n' > /etc/apt/preferences.d/nosnap.pref
|
||||
|
||||
# Ensure user/group tools exist
|
||||
apt-get install -y passwd adduser || true
|
||||
getent group i2c >/dev/null || /usr/sbin/groupadd i2c
|
||||
|
||||
# Base packages
|
||||
BASE_PKGS="openssh-server git i2c-tools python3-smbus python3-pil zbar-tools qrencode lm-sensors"
|
||||
apt-get install -y $BASE_PKGS
|
||||
|
||||
# ------- OLED (Luma) -------
|
||||
# Prefer distro package; fall back to pip if not present in this release
|
||||
if ! dpkg -s python3-luma.oled >/dev/null 2>&1; then
|
||||
apt-get update
|
||||
if ! apt-get install -y python3-luma.oled; then
|
||||
apt-get install -y python3-pip
|
||||
pip3 install --no-input --break-system-packages luma.oled
|
||||
fi
|
||||
fi
|
||||
|
||||
# ------- Camera apps -------
|
||||
# Ubuntu renamed libcamera-apps -> rpicam-apps for Raspberry Pi.
|
||||
# Try in order; tolerate absence (the box might be display-only).
|
||||
apt-get update
|
||||
if ! apt-get install -y rpicam-apps; then
|
||||
apt-get install -y libcamera-apps || apt-get install -y libcamera-tools || true
|
||||
fi
|
||||
|
||||
# Enable SSH on boot (no systemctl in chroot)
|
||||
mkdir -p /etc/systemd/system/multi-user.target.wants
|
||||
ln -sf /lib/systemd/system/ssh.service /etc/systemd/system/multi-user.target.wants/ssh.service
|
||||
|
||||
# Create user and set password
|
||||
if ! id -u STYX_USER >/dev/null 2>&1; then
|
||||
/usr/sbin/useradd -m -s /bin/bash -G sudo,video,i2c STYX_USER
|
||||
fi
|
||||
echo 'STYX_USER:STYX_PASS' | /usr/sbin/chpasswd
|
||||
|
||||
# Optional: preload SSH key
|
||||
if [ -n 'SSH_PUBKEY' ] && echo 'SSH_PUBKEY' | grep -q 'ssh-'; then
|
||||
install -d -m700 /home/STYX_USER/.ssh
|
||||
echo 'SSH_PUBKEY' >> /home/STYX_USER/.ssh/authorized_keys
|
||||
chmod 600 /home/STYX_USER/.ssh/authorized_keys
|
||||
chown -R STYX_USER:STYX_USER /home/STYX_USER/.ssh
|
||||
fi
|
||||
|
||||
# Freenove code
|
||||
git clone https://github.com/Freenove/Freenove_Computer_Case_Kit_for_Raspberry_Pi.git /opt/freenove || true
|
||||
|
||||
# Hostname
|
||||
echo 'STYX_HOSTNAME' > /etc/hostname
|
||||
if grep -q '^127\.0\.1\.1' /etc/hosts; then
|
||||
sed -i 's/^127\.0\.1\.1.*/127.0.1.1\tSTYX_HOSTNAME/' /etc/hosts
|
||||
else
|
||||
echo -e '127.0.1.1\tSTYX_HOSTNAME' >> /etc/hosts
|
||||
fi
|
||||
|
||||
apt-get clean
|
||||
EOS
|
||||
)
|
||||
# Inject config values safely
|
||||
CHCMD="${CHCMD//STYX_USER/${STYX_USER}}"
|
||||
CHCMD="${CHCMD//STYX_PASS/${STYX_PASS}}"
|
||||
CHCMD="${CHCMD//STYX_HOSTNAME/${STYX_HOSTNAME}}"
|
||||
CHCMD="${CHCMD//SSH_PUBKEY/${SSH_PUBKEY}}"
|
||||
|
||||
chroot /mnt/pi-root /bin/bash -lc "$CHCMD"
|
||||
}
|
||||
|
||||
install_service_host() {
|
||||
# Systemd unit for the Freenove example app
|
||||
mkdir -p /mnt/pi-root/etc/systemd/system/multi-user.target.wants
|
||||
cat >/mnt/pi-root/etc/systemd/system/freenove-case.service <<'SERVICE'
|
||||
[Unit]
|
||||
Description=Freenove Case OLED/Fans/LEDs
|
||||
After=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/python3 /opt/freenove/Code/application.py
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SERVICE
|
||||
ln -sf /etc/systemd/system/freenove-case.service \
|
||||
/mnt/pi-root/etc/systemd/system/multi-user.target.wants/freenove-case.service || true
|
||||
}
|
||||
|
||||
boot_tweaks() {
|
||||
# Enable I2C and set DSI panel on the BOOT partition
|
||||
grep -q 'dtparam=i2c_arm=on' /mnt/pi-boot/config.txt || echo 'dtparam=i2c_arm=on' >> /mnt/pi-boot/config.txt
|
||||
# Append kernel cmdline only once
|
||||
if ! grep -q 'DSI-1:800x480@60D' /mnt/pi-boot/cmdline.txt 2>/dev/null; then
|
||||
sed -i '1 s#$# video=DSI-1:800x480@60D video=HDMI-A-1:off video=HDMI-A-2:off#' /mnt/pi-boot/cmdline.txt || true
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
rm -f /mnt/pi-root/usr/sbin/policy-rc.d || true
|
||||
for d in dev/pts dev proc sys; do umount -lf "/mnt/pi-root/$d" 2>/dev/null || true; done
|
||||
umount -lf /mnt/pi-boot 2>/dev/null || true
|
||||
umount -lf /mnt/pi-root 2>/dev/null || true
|
||||
sync || true
|
||||
}
|
||||
|
||||
main() {
|
||||
require_root
|
||||
ensure_binfmt_arm64
|
||||
find_parts
|
||||
trap 'echo "ERROR at line $LINENO" >&2; cleanup' ERR INT
|
||||
mount_parts
|
||||
prep_chroot
|
||||
install_service_host
|
||||
boot_tweaks
|
||||
cleanup
|
||||
echo "✅ Done. Move the NVMe to the Pi and boot."
|
||||
echo " Login: user '${STYX_USER}' pass '${STYX_PASS}' (change with 'passwd')."
|
||||
echo " Quick checks on the Pi:"
|
||||
echo " sudo i2cdetect -y 1"
|
||||
echo " rpicam-still -n -o test.jpg # (if rpicam-apps installed)"
|
||||
echo " libcamera-still -n -o test.jpg # (if legacy libcamera-apps installed)"
|
||||
echo " systemctl status freenove-case"
|
||||
}
|
||||
main "$@"
|
||||
@ -23,6 +23,11 @@ spec:
|
||||
spec:
|
||||
nodeSelector:
|
||||
jellyfin: "true"
|
||||
securityContext:
|
||||
runAsUser: 1000
|
||||
fsGroup: 65532
|
||||
fsGroupChangePolicy: OnRootMismatch
|
||||
runAsGroup: 65532
|
||||
runtimeClassName: nvidia
|
||||
containers:
|
||||
- name: jellyfin
|
||||
@ -36,6 +41,12 @@ spec:
|
||||
value: "compute,video,utility"
|
||||
- name: JELLYFIN_PublishedServerUrl
|
||||
value: "https://stream.bstein.dev"
|
||||
- name: PUID
|
||||
value: "1000"
|
||||
- name: PGID
|
||||
value: "65532"
|
||||
- name: UMASK
|
||||
value: "002"
|
||||
resources:
|
||||
limits:
|
||||
nvidia.com/gpu: 1
|
||||
@ -64,4 +75,4 @@ spec:
|
||||
claimName: jellyfin-cache-astreae
|
||||
- name: media
|
||||
persistentVolumeClaim:
|
||||
claimName: jellyfin-media-asteria
|
||||
claimName: jellyfin-media-asteria-new
|
||||
|
||||
@ -36,5 +36,19 @@ spec:
|
||||
accessModes: ["ReadWriteMany"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Ti
|
||||
storage: 4Ti
|
||||
storageClassName: asteria
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: jellyfin-media-asteria-new
|
||||
namespace: jellyfin
|
||||
spec:
|
||||
accessModes: ["ReadWriteMany"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 4Ti
|
||||
storageClassName: asteria
|
||||
|
||||
171
services/jitsi/deployment.yaml
Normal file
171
services/jitsi/deployment.yaml
Normal file
@ -0,0 +1,171 @@
|
||||
# services/jitsi/deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: jitsi-prosody
|
||||
namespace: jitsi
|
||||
spec:
|
||||
replicas: 0
|
||||
selector:
|
||||
matchLabels: { app: jitsi-prosody }
|
||||
template:
|
||||
metadata:
|
||||
labels: { app: jitsi-prosody }
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: titan-22
|
||||
kubernetes.io/arch: amd64
|
||||
containers:
|
||||
- name: prosody
|
||||
image: jitsi/prosody:stable
|
||||
ports:
|
||||
- { name: c2s, containerPort: 5222, protocol: TCP }
|
||||
- { name: http, containerPort: 5280, protocol: TCP }
|
||||
- { name: comp, containerPort: 5347, protocol: TCP }
|
||||
env:
|
||||
- { name: XMPP_DOMAIN, value: "meet.jitsi" }
|
||||
- { name: XMPP_AUTH_DOMAIN, value: "auth.meet.jitsi" }
|
||||
- { name: XMPP_MUC_DOMAIN, value: "muc.meet.jitsi" }
|
||||
- { name: XMPP_INTERNAL_MUC_DOMAIN, value: "internal-muc.meet.jitsi" }
|
||||
- { name: ENABLE_AUTH, value: "0" } # open instance, no auth (fastest path)
|
||||
- { name: ENABLE_GUESTS, value: "1" }
|
||||
- { name: JICOFO_AUTH_USER, value: "focus" }
|
||||
- { name: JVB_AUTH_USER, value: "jvb" }
|
||||
- name: JICOFO_AUTH_PASSWORD
|
||||
valueFrom: { secretKeyRef: { name: jitsi-internal-secrets, key: JICOFO_AUTH_PASSWORD } }
|
||||
- name: JICOFO_COMPONENT_SECRET
|
||||
valueFrom: { secretKeyRef: { name: jitsi-internal-secrets, key: JICOFO_COMPONENT_SECRET } }
|
||||
- name: JVB_AUTH_PASSWORD
|
||||
valueFrom: { secretKeyRef: { name: jitsi-internal-secrets, key: JVB_AUTH_PASSWORD } }
|
||||
volumeMounts:
|
||||
- { name: cfg, mountPath: /config }
|
||||
volumes:
|
||||
- name: cfg
|
||||
persistentVolumeClaim: { claimName: jitsi-prosody-config }
|
||||
|
||||
---
|
||||
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: jitsi-jicofo
|
||||
namespace: jitsi
|
||||
spec:
|
||||
replicas: 0
|
||||
selector:
|
||||
matchLabels: { app: jitsi-jicofo }
|
||||
template:
|
||||
metadata:
|
||||
labels: { app: jitsi-jicofo }
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: titan-22
|
||||
kubernetes.io/arch: amd64
|
||||
containers:
|
||||
- name: jicofo
|
||||
image: jitsi/jicofo:stable
|
||||
env:
|
||||
- { name: XMPP_DOMAIN, value: "meet.jitsi" }
|
||||
- { name: XMPP_AUTH_DOMAIN, value: "auth.meet.jitsi" }
|
||||
- { name: XMPP_MUC_DOMAIN, value: "muc.meet.jitsi" }
|
||||
- { name: XMPP_INTERNAL_MUC_DOMAIN, value: "internal-muc.meet.jitsi" }
|
||||
- { name: XMPP_SERVER, value: "jitsi-prosody.jitsi.svc.cluster.local" }
|
||||
- { name: JICOFO_AUTH_USER, value: "focus" }
|
||||
- name: JICOFO_AUTH_PASSWORD
|
||||
valueFrom: { secretKeyRef: { name: jitsi-internal-secrets, key: JICOFO_AUTH_PASSWORD } }
|
||||
- name: JICOFO_COMPONENT_SECRET
|
||||
valueFrom: { secretKeyRef: { name: jitsi-internal-secrets, key: JICOFO_COMPONENT_SECRET } }
|
||||
- { name: JVB_BREWERY_MUC, value: "jvbbrewery" }
|
||||
volumeMounts:
|
||||
- { name: cfg, mountPath: /config }
|
||||
volumes:
|
||||
- name: cfg
|
||||
persistentVolumeClaim: { claimName: jitsi-jicofo-config }
|
||||
|
||||
---
|
||||
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: jitsi-jvb
|
||||
namespace: jitsi
|
||||
spec:
|
||||
replicas: 0
|
||||
selector:
|
||||
matchLabels: { app: jitsi-jvb }
|
||||
template:
|
||||
metadata:
|
||||
labels: { app: jitsi-jvb }
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: titan-22
|
||||
kubernetes.io/arch: amd64
|
||||
containers:
|
||||
- name: jvb
|
||||
image: jitsi/jvb:stable
|
||||
ports:
|
||||
- { name: colibri-ws, containerPort: 9090, protocol: TCP } # WebSocket control channel
|
||||
- { name: rtp-udp, containerPort: 10000, hostPort: 10000, protocol: UDP } # media
|
||||
- { name: rtp-tcp, containerPort: 4443, hostPort: 4443, protocol: TCP }
|
||||
env:
|
||||
- { name: XMPP_DOMAIN, value: "meet.jitsi" }
|
||||
- { name: XMPP_AUTH_DOMAIN, value: "auth.meet.jitsi" }
|
||||
- { name: XMPP_MUC_DOMAIN, value: "muc.meet.jitsi" }
|
||||
- { name: XMPP_INTERNAL_MUC_DOMAIN, value: "internal-muc.meet.jitsi" }
|
||||
- { name: XMPP_SERVER, value: "jitsi-prosody.jitsi.svc.cluster.local" }
|
||||
- { name: JVB_AUTH_USER, value: "jvb" }
|
||||
- name: JVB_AUTH_PASSWORD
|
||||
valueFrom: { secretKeyRef: { name: jitsi-internal-secrets, key: JVB_AUTH_PASSWORD } }
|
||||
- { name: JVB_BREWERY_MUC, value: "jvbbrewery" }
|
||||
- { name: JVB_PORT, value: "10000" } # matches hostPort above
|
||||
- { name: ENABLE_COLIBRI_WEBSOCKET, value: "1" } # enables /colibri-ws
|
||||
# - { name: JVB_STUN_SERVERS, value: "stun.l.google.com:19302,stun1.l.google.com:19302,meet-jit-si-turnrelay.jitsi.net:443" }
|
||||
- { name: JVB_ENABLE_APIS, value: "rest,colibri" }
|
||||
- { name: JVB_WS_DOMAIN, value: "meet.bstein.dev:443" }
|
||||
- { name: JVB_WS_TLS, value: "true" }
|
||||
- { name: JVB_ADVERTISE_IPS, value: "38.28.125.112" }
|
||||
- { name: JVB_TCP_HARVESTER_DISABLED, value: "false" }
|
||||
- { name: JVB_TCP_PORT, value: "4443" }
|
||||
volumeMounts:
|
||||
- { name: cfg, mountPath: /config }
|
||||
volumes:
|
||||
- name: cfg
|
||||
persistentVolumeClaim: { claimName: jitsi-jvb-config }
|
||||
|
||||
---
|
||||
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: jitsi-web
|
||||
namespace: jitsi
|
||||
spec:
|
||||
replicas: 0
|
||||
selector:
|
||||
matchLabels: { app: jitsi-web }
|
||||
template:
|
||||
metadata:
|
||||
labels: { app: jitsi-web }
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: titan-22
|
||||
kubernetes.io/arch: amd64
|
||||
containers:
|
||||
- name: web
|
||||
image: jitsi/web:stable
|
||||
ports:
|
||||
- { name: http, containerPort: 80, protocol: TCP }
|
||||
env:
|
||||
- { name: PUBLIC_URL, value: "https://meet.bstein.dev" }
|
||||
- { name: XMPP_DOMAIN, value: "meet.jitsi" }
|
||||
- { name: XMPP_AUTH_DOMAIN, value: "auth.meet.jitsi" }
|
||||
- { name: XMPP_MUC_DOMAIN, value: "muc.meet.jitsi" }
|
||||
- { name: XMPP_INTERNAL_MUC_DOMAIN, value: "internal-muc.meet.jitsi" }
|
||||
- { name: XMPP_BOSH_URL_BASE, value: "https://meet.bstein.dev" }
|
||||
- { name: ENABLE_XMPP_WEBSOCKET, value: "1" }
|
||||
- { name: ENABLE_COLIBRI_WEBSOCKET, value: "1" }
|
||||
volumeMounts:
|
||||
- { name: cfg, mountPath: /config }
|
||||
volumes:
|
||||
- name: cfg
|
||||
persistentVolumeClaim: { claimName: jitsi-web-config }
|
||||
41
services/jitsi/ingress.yaml
Normal file
41
services/jitsi/ingress.yaml
Normal file
@ -0,0 +1,41 @@
|
||||
# services/jitsi/ingress.yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: jitsi
|
||||
namespace: jitsi
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts: [ "meet.bstein.dev" ]
|
||||
secretName: jitsi-meet-tls
|
||||
rules:
|
||||
- host: meet.bstein.dev
|
||||
http:
|
||||
paths:
|
||||
- path: /colibri-ws
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: jitsi-jvb
|
||||
port: { number: 9090 }
|
||||
- path: /xmpp-websocket
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: jitsi-prosody
|
||||
port: { number: 5280 }
|
||||
- path: /http-bind
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: jitsi-prosody
|
||||
port: { number: 5280 }
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: jitsi-web
|
||||
port: { number: 80 }
|
||||
10
services/jitsi/kustomization.yaml
Normal file
10
services/jitsi/kustomization.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
# services/jitsi/kustomization.yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- pvc.yaml
|
||||
- ingress.yaml
|
||||
- secret.yaml
|
||||
5
services/jitsi/namespace.yaml
Normal file
5
services/jitsi/namespace.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
# services/jitsi/namespace.yaml
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: jitsi
|
||||
42
services/jitsi/pvc.yaml
Normal file
42
services/jitsi/pvc.yaml
Normal file
@ -0,0 +1,42 @@
|
||||
# services/jitsi/pvc.yaml
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: jitsi-web-config
|
||||
namespace: jitsi
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources: { requests: { storage: 10Gi } }
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: jitsi-prosody-config
|
||||
namespace: jitsi
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources: { requests: { storage: 10Gi } }
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: jitsi-jicofo-config
|
||||
namespace: jitsi
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources: { requests: { storage: 10Gi } }
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: jitsi-jvb-config
|
||||
namespace: jitsi
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources: { requests: { storage: 10Gi } }
|
||||
11
services/jitsi/secret.yaml
Normal file
11
services/jitsi/secret.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
# services/jitsi/secret.yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: jitsi-internal-secrets
|
||||
namespace: jitsi
|
||||
type: Opaque
|
||||
data:
|
||||
JICOFO_COMPONENT_SECRET: bEg5Y09hZFJBem5PUFliQlp4RHkwRTRP
|
||||
JICOFO_AUTH_PASSWORD: VVkyUmczaVRDWUZ0MzdQdmN3UDN1SFc5
|
||||
JVB_AUTH_PASSWORD: d0M5aWJ4dWlPTnhFak9lRHJqSHdYa0g5
|
||||
36
services/jitsi/service.yaml
Normal file
36
services/jitsi/service.yaml
Normal file
@ -0,0 +1,36 @@
|
||||
# services/jitsi/service.yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: jitsi-prosody
|
||||
namespace: jitsi
|
||||
spec:
|
||||
selector: { app: jitsi-prosody }
|
||||
ports:
|
||||
- { name: c2s, port: 5222, targetPort: 5222, protocol: TCP }
|
||||
- { name: http, port: 5280, targetPort: 5280, protocol: TCP }
|
||||
- { name: comp, port: 5347, targetPort: 5347, protocol: TCP }
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: jitsi-jvb
|
||||
namespace: jitsi
|
||||
spec:
|
||||
selector: { app: jitsi-jvb }
|
||||
ports:
|
||||
- { name: colibri-ws, port: 9090, targetPort: 9090, protocol: TCP }
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: jitsi-web
|
||||
namespace: jitsi
|
||||
spec:
|
||||
selector: { app: jitsi-web }
|
||||
ports:
|
||||
- { name: http, port: 80, targetPort: 80, protocol: TCP }
|
||||
206
services/monitoring/helmrelease.yaml
Normal file
206
services/monitoring/helmrelease.yaml
Normal file
@ -0,0 +1,206 @@
|
||||
# services/monitoring/kube-state-metrics-helmrelease.yaml
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: kube-state-metrics
|
||||
namespace: monitoring
|
||||
spec:
|
||||
interval: 15m
|
||||
chart:
|
||||
spec:
|
||||
chart: kube-state-metrics
|
||||
version: "~6.0.0"
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: prometheus-community
|
||||
namespace: flux-system
|
||||
values:
|
||||
prometheusScrape: true # annotates for /metrics auto-scrape. :contentReference[oaicite:16]{index=16}
|
||||
service:
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "8080" # ksm serves metrics on 8080 by default
|
||||
prometheus.io/path: "/metrics"
|
||||
|
||||
---
|
||||
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: node-exporter
|
||||
namespace: monitoring
|
||||
spec:
|
||||
interval: 15m
|
||||
chart:
|
||||
spec:
|
||||
chart: prometheus-node-exporter
|
||||
version: "~4.0.0"
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: prometheus-community
|
||||
namespace: flux-system
|
||||
values:
|
||||
service:
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "9100"
|
||||
|
||||
---
|
||||
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: victoria-metrics-single
|
||||
namespace: monitoring
|
||||
spec:
|
||||
interval: 15m
|
||||
chart:
|
||||
spec:
|
||||
chart: victoria-metrics-single
|
||||
version: "~0.15.0" # or omit to track appVersion
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: victoria-metrics
|
||||
namespace: flux-system
|
||||
values:
|
||||
server:
|
||||
# keep ~3 months; change as you like (supports "d", "y")
|
||||
extraArgs:
|
||||
retentionPeriod: "90d" # VM flag -retentionPeriod=90d. :contentReference[oaicite:11]{index=11}
|
||||
|
||||
persistentVolume:
|
||||
enabled: true
|
||||
size: 100Gi # adjust; uses default StorageClass (Longhorn)
|
||||
# storageClassName: "" # set if you want a specific class
|
||||
|
||||
# Enable built-in Kubernetes scraping
|
||||
scrape:
|
||||
enabled: true # chart enables promscrape. :contentReference[oaicite:12]{index=12}
|
||||
config:
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
|
||||
scrape_configs:
|
||||
# VM self-metrics
|
||||
- job_name: victoriametrics
|
||||
static_configs:
|
||||
- targets: ["localhost:8428"]
|
||||
|
||||
# --- K8s control-plane & nodes (from VM docs guide) ---
|
||||
- job_name: "kubernetes-apiservers"
|
||||
kubernetes_sd_configs: [{ role: endpoints }]
|
||||
scheme: https
|
||||
tls_config:
|
||||
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
insecure_skip_verify: true
|
||||
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
relabel_configs:
|
||||
- action: keep
|
||||
source_labels: [__meta_kubernetes_namespace,__meta_kubernetes_service_name,__meta_kubernetes_endpoint_port_name]
|
||||
regex: default;kubernetes;https
|
||||
|
||||
- job_name: "kubernetes-nodes"
|
||||
scheme: https
|
||||
tls_config:
|
||||
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
insecure_skip_verify: true
|
||||
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
kubernetes_sd_configs: [{ role: node }]
|
||||
relabel_configs:
|
||||
- action: labelmap
|
||||
regex: __meta_kubernetes_node_label_(.+)
|
||||
- target_label: __address__
|
||||
replacement: kubernetes.default.svc:443
|
||||
- source_labels: [__meta_kubernetes_node_name]
|
||||
regex: (.+)
|
||||
target_label: __metrics_path__
|
||||
replacement: /api/v1/nodes/$1/proxy/metrics
|
||||
|
||||
- job_name: "kubernetes-nodes-cadvisor"
|
||||
scheme: https
|
||||
tls_config:
|
||||
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
insecure_skip_verify: true
|
||||
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
kubernetes_sd_configs: [{ role: node }]
|
||||
relabel_configs:
|
||||
- action: labelmap
|
||||
regex: __meta_kubernetes_node_label_(.+)
|
||||
- target_label: __address__
|
||||
replacement: kubernetes.default.svc:443
|
||||
- source_labels: [__meta_kubernetes_node_name]
|
||||
regex: (.+)
|
||||
target_label: __metrics_path__
|
||||
replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor
|
||||
|
||||
# --- Annotated Services (generic autodiscovery) ---
|
||||
- job_name: "kubernetes-service-endpoints"
|
||||
kubernetes_sd_configs: [{ role: endpoints }]
|
||||
relabel_configs:
|
||||
- action: keep
|
||||
source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
|
||||
regex: "true"
|
||||
- action: replace
|
||||
source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
|
||||
regex: (https?)
|
||||
target_label: __scheme__
|
||||
- action: replace
|
||||
source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
|
||||
target_label: __metrics_path__
|
||||
- action: replace
|
||||
regex: (.+)(?::\d+);(\d+)
|
||||
replacement: $1:$2
|
||||
source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
|
||||
target_label: __address__
|
||||
|
||||
# --- Annotated Pods (generic autodiscovery) ---
|
||||
- job_name: "kubernetes-pods"
|
||||
kubernetes_sd_configs: [{ role: pod }]
|
||||
relabel_configs:
|
||||
- action: keep
|
||||
source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
|
||||
regex: "true"
|
||||
- action: replace
|
||||
source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
|
||||
target_label: __metrics_path__
|
||||
- action: replace
|
||||
regex: (.+):(?:\d+);(\d+)
|
||||
replacement: $1:$2
|
||||
source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
|
||||
target_label: __address__
|
||||
|
||||
# --- kube-state-metrics (via its Service) ---
|
||||
- job_name: "kube-state-metrics"
|
||||
kubernetes_sd_configs: [{ role: endpoints }]
|
||||
relabel_configs:
|
||||
- action: keep
|
||||
source_labels: [__meta_kubernetes_service_label_app_kubernetes_io_name]
|
||||
regex: kube-state-metrics
|
||||
|
||||
# --- Longhorn ---
|
||||
- job_name: "longhorn-backend"
|
||||
static_configs:
|
||||
- targets: ["longhorn-backend.longhorn-system.svc:9500"]
|
||||
metrics_path: /metrics
|
||||
|
||||
# --- cert-manager (pods expose on 9402) ---
|
||||
- job_name: "cert-manager"
|
||||
kubernetes_sd_configs: [{ role: pod }]
|
||||
relabel_configs:
|
||||
- action: keep
|
||||
source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_pod_label_app_kubernetes_io_name]
|
||||
regex: cert-manager;cert-manager
|
||||
- action: replace
|
||||
source_labels: [__address__]
|
||||
regex: "(.+):\\d+"
|
||||
replacement: "$1:9402"
|
||||
target_label: __address__
|
||||
|
||||
# --- Flux controllers (default :8080/metrics) ---
|
||||
- job_name: "flux"
|
||||
kubernetes_sd_configs: [{ role: pod }]
|
||||
relabel_configs:
|
||||
- action: keep
|
||||
source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_pod_label_app_kubernetes_io_part_of]
|
||||
regex: flux-system;flux
|
||||
|
||||
8
services/monitoring/kustomization.yaml
Normal file
8
services/monitoring/kustomization.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
# services/monitoring/kustomization.yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: monitoring
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- rbac.yaml
|
||||
- helmrelease.yaml
|
||||
4
services/monitoring/namespace.yaml
Normal file
4
services/monitoring/namespace.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: monitoring
|
||||
33
services/monitoring/rbac.yaml
Normal file
33
services/monitoring/rbac.yaml
Normal file
@ -0,0 +1,33 @@
|
||||
# services/monitoring/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: vmsingle-scrape
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources:
|
||||
- nodes
|
||||
- nodes/proxy
|
||||
- nodes/metrics
|
||||
- services
|
||||
- endpoints
|
||||
- pods
|
||||
verbs: ["get","list","watch"]
|
||||
- apiGroups: ["networking.k8s.io","extensions"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get","list","watch"]
|
||||
|
||||
---
|
||||
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: vmsingle-scrape
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: vmsingle-scrape
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: victoria-metrics-single
|
||||
namespace: monitoring
|
||||
48
services/pegasus/configmap.yaml
Normal file
48
services/pegasus/configmap.yaml
Normal file
@ -0,0 +1,48 @@
|
||||
# services/pegasus/configmap.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: pegasus-config
|
||||
namespace: jellyfin
|
||||
data:
|
||||
PEGASUS_MEDIA_ROOT: "/media"
|
||||
PEGASUS_BIND: ":8080"
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: pegasus-user-map
|
||||
namespace: jellyfin
|
||||
data:
|
||||
user-map.yaml: |
|
||||
map:
|
||||
mary_grace_allison:
|
||||
- "allison"
|
||||
- "gavilan"
|
||||
maddie_rejcek:
|
||||
- "rangeejcek"
|
||||
- "gavilan"
|
||||
carol_mcguinness:
|
||||
- "mcguinness_family"
|
||||
- "gavilan"
|
||||
ed_stein:
|
||||
- "stein_family"
|
||||
- "gavilan"
|
||||
brad_stein:
|
||||
- "stein_brad"
|
||||
- "gavilan"
|
||||
lisa_stein:
|
||||
- "scott"
|
||||
channa_cox:
|
||||
- "cox"
|
||||
- "gavilan"
|
||||
chelie_sheehan:
|
||||
- "sheehan"
|
||||
- "gavilan"
|
||||
olya_bulgakova:
|
||||
- "bulgakova"
|
||||
sean_mcguinness:
|
||||
- "mcguinness_sean"
|
||||
- "gavilan"
|
||||
114
services/pegasus/deployment.yaml
Normal file
114
services/pegasus/deployment.yaml
Normal file
@ -0,0 +1,114 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: pegasus
|
||||
namespace: jellyfin
|
||||
spec:
|
||||
replicas: 0
|
||||
revisionHistoryLimit: 3
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 0
|
||||
maxUnavailable: 1
|
||||
selector: { matchLabels: { app: pegasus } }
|
||||
template:
|
||||
metadata: { labels: { app: pegasus } }
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/arch: amd64
|
||||
imagePullSecrets:
|
||||
- name: zot-regcred
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
fsGroup: 65532
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
initContainers:
|
||||
- name: fix-perms
|
||||
image: alpine:3.20
|
||||
command:
|
||||
- sh
|
||||
- -lc
|
||||
- |
|
||||
set -eux
|
||||
|
||||
# Scratch area for tus uploads (always writable)
|
||||
mkdir -p /media/.pegasus-tus
|
||||
chmod 0777 /media/.pegasus-tus
|
||||
|
||||
# Make each top-level library dir group-writable and setgid,
|
||||
# and try to set its group to 65532 (so the app can write).
|
||||
for d in /media/*; do
|
||||
[ -d "$d" ] || continue
|
||||
base="$(basename "$d")"
|
||||
[ "$base" = ".pegasus-tus" ] && continue
|
||||
# chgrp can fail on some backends; don't block the pod if it does.
|
||||
chgrp 65532 "$d" || true
|
||||
chmod 2775 "$d" || true
|
||||
done
|
||||
securityContext:
|
||||
runAsUser: 0
|
||||
runAsGroup: 0
|
||||
runAsNonRoot: false
|
||||
allowPrivilegeEscalation: false
|
||||
volumeMounts:
|
||||
- { name: media, mountPath: /media }
|
||||
|
||||
containers:
|
||||
- name: pegasus
|
||||
image: registry.bstein.dev/pegasus:1.2.32 # {"$imagepolicy": "jellyfin:pegasus"}
|
||||
imagePullPolicy: Always
|
||||
command: ["/pegasus"]
|
||||
env:
|
||||
- name: PEGASUS_MEDIA_ROOT
|
||||
valueFrom: { configMapKeyRef: { name: pegasus-config, key: PEGASUS_MEDIA_ROOT } }
|
||||
- name: PEGASUS_BIND
|
||||
valueFrom: { configMapKeyRef: { name: pegasus-config, key: PEGASUS_BIND } }
|
||||
- name: PEGASUS_USER_MAP_FILE
|
||||
value: "/config/user-map.yaml"
|
||||
- name: PEGASUS_SESSION_KEY
|
||||
valueFrom: { secretKeyRef: { name: pegasus-secrets, key: PEGASUS_SESSION_KEY } }
|
||||
- name: JELLYFIN_URL
|
||||
valueFrom: { secretKeyRef: { name: pegasus-secrets, key: JELLYFIN_URL } }
|
||||
- name: JELLYFIN_API_KEY
|
||||
valueFrom: { secretKeyRef: { name: pegasus-secrets, key: JELLYFIN_API_KEY } }
|
||||
- name: PEGASUS_DEBUG
|
||||
value: "1"
|
||||
- name: PEGASUS_DRY_RUN
|
||||
value: "0"
|
||||
ports: [{ name: http, containerPort: 8080 }]
|
||||
readinessProbe:
|
||||
httpGet: { path: /healthz, port: http }
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 1
|
||||
livenessProbe:
|
||||
httpGet: { path: /healthz, port: http }
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 2
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities: { drop: ["ALL"] }
|
||||
resources:
|
||||
requests: { cpu: 100m, memory: 256Mi }
|
||||
limits: { cpu: 1000m, memory: 1Gi }
|
||||
volumeMounts:
|
||||
- name: media
|
||||
mountPath: /media
|
||||
- name: config
|
||||
mountPath: /config
|
||||
readOnly: true
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
volumes:
|
||||
- name: media
|
||||
persistentVolumeClaim:
|
||||
claimName: jellyfin-media-asteria-new
|
||||
- name: config
|
||||
configMap: { name: pegasus-user-map }
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
25
services/pegasus/image.yaml
Normal file
25
services/pegasus/image.yaml
Normal file
@ -0,0 +1,25 @@
|
||||
# services/pegasus/image.yaml
|
||||
apiVersion: image.toolkit.fluxcd.io/v1beta2
|
||||
kind: ImageRepository
|
||||
metadata:
|
||||
name: pegasus
|
||||
namespace: jellyfin
|
||||
spec:
|
||||
image: registry.bstein.dev/pegasus
|
||||
interval: 1m0s
|
||||
secretRef:
|
||||
name: zot-regcred
|
||||
|
||||
---
|
||||
|
||||
apiVersion: image.toolkit.fluxcd.io/v1beta2
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
name: pegasus
|
||||
namespace: jellyfin
|
||||
spec:
|
||||
imageRepositoryRef:
|
||||
name: pegasus
|
||||
policy:
|
||||
semver:
|
||||
range: "1.x"
|
||||
25
services/pegasus/ingress.yaml
Normal file
25
services/pegasus/ingress.yaml
Normal file
@ -0,0 +1,25 @@
|
||||
# services/pegasus/ingress.yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: pegasus
|
||||
namespace: jellyfin
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: traefik
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
spec:
|
||||
tls:
|
||||
- hosts: [ "pegasus.bstein.dev" ]
|
||||
secretName: pegasus-bstein-dev-tls
|
||||
rules:
|
||||
- host: pegasus.bstein.dev
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: /
|
||||
backend:
|
||||
service:
|
||||
name: pegasus
|
||||
port: { number: 80 }
|
||||
21
services/pegasus/kustomization.yaml
Normal file
21
services/pegasus/kustomization.yaml
Normal file
@ -0,0 +1,21 @@
|
||||
# services/pegasus/kustomization.yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- configmap.yaml
|
||||
- service.yaml
|
||||
- deployment.yaml
|
||||
- ingress.yaml
|
||||
patches:
|
||||
- target: { kind: Deployment, name: pegasus, namespace: jellyfin }
|
||||
patch: |
|
||||
- op: add
|
||||
path: /spec/template/spec/containers/-
|
||||
value:
|
||||
name: shell
|
||||
image: alpine:3.20
|
||||
command: ["sh","-c","sleep infinity"]
|
||||
securityContext: { runAsUser: 10001, runAsGroup: 10001, allowPrivilegeEscalation: false }
|
||||
volumeMounts:
|
||||
- { name: media, mountPath: /media }
|
||||
- { name: config, mountPath: /config, readOnly: true }
|
||||
12
services/pegasus/service.yaml
Normal file
12
services/pegasus/service.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
# services/pegasus/service.yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: pegasus
|
||||
namespace: jellyfin
|
||||
spec:
|
||||
selector: { app: pegasus }
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: http
|
||||
@ -17,15 +17,19 @@ data:
|
||||
"http": {
|
||||
"address": "0.0.0.0",
|
||||
"port": "5000",
|
||||
"auth": { "htpasswd": { "path": "/etc/zot/htpasswd" } },
|
||||
"realm": "zot-registry",
|
||||
"compat": ["docker2s2"],
|
||||
"auth": {
|
||||
"htpasswd": { "path": "/etc/zot/htpasswd" }
|
||||
},
|
||||
"accessControl": {
|
||||
"repositories": {
|
||||
"**": {
|
||||
"policies": [
|
||||
{ "users": ["bstein"], "actions": ["read", "create", "update", "delete"] }
|
||||
],
|
||||
"defaultPolicy": ["read"],
|
||||
"anonymousPolicy": ["read"]
|
||||
"defaultPolicy": [],
|
||||
"anonymousPolicy": []
|
||||
}
|
||||
},
|
||||
"adminPolicy": {
|
||||
@ -36,8 +40,8 @@ data:
|
||||
},
|
||||
"log": { "level": "info" },
|
||||
"extensions": {
|
||||
"ui": { "enable": true },
|
||||
"search": { "enable": true },
|
||||
"ui": { "enable": true },
|
||||
"search": { "enable": true },
|
||||
"metrics": { "enable": true }
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,8 @@ spec:
|
||||
values: ["rpi4"]
|
||||
containers:
|
||||
- name: zot
|
||||
image: ghcr.io/project-zot/zot-linux-arm64:latest
|
||||
image: ghcr.io/project-zot/zot-linux-arm64:v2.1.8
|
||||
imagePullPolicy: IfNotPresent
|
||||
args: ["serve", "/etc/zot/config.json"]
|
||||
ports:
|
||||
- { name: http, containerPort: 5000 }
|
||||
@ -48,13 +49,15 @@ spec:
|
||||
- name: zot-data
|
||||
mountPath: /var/lib/registry
|
||||
readinessProbe:
|
||||
httpGet: { path: /v2/, port: http }
|
||||
tcpSocket:
|
||||
port: 5000
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 5
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 5000
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet: { path: /v2/, port: http }
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
resources:
|
||||
requests: { cpu: "50m", memory: "64Mi" }
|
||||
volumes:
|
||||
|
||||
@ -6,6 +6,9 @@ metadata:
|
||||
namespace: zot
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
traefik.ingress.kubernetes.io/router.middlewares: zot-zot-resp-headers@kubernetescrd
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
|
||||
@ -8,4 +8,4 @@ resources:
|
||||
- configmap.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
|
||||
- middleware.yaml
|
||||
|
||||
26
services/zot/middleware.yaml
Normal file
26
services/zot/middleware.yaml
Normal file
@ -0,0 +1,26 @@
|
||||
# services/zot/middleware.yaml
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: zot-resp-headers
|
||||
namespace: zot
|
||||
spec:
|
||||
headers:
|
||||
customResponseHeaders:
|
||||
Docker-Distribution-Api-Version: "registry/2.0"
|
||||
accessControlAllowOriginList:
|
||||
- "*"
|
||||
accessControlAllowCredentials: true
|
||||
accessControlAllowHeaders:
|
||||
- Authorization
|
||||
- Content-Type
|
||||
- Docker-Distribution-Api-Version
|
||||
- X-Registry-Auth
|
||||
accessControlAllowMethods:
|
||||
- GET
|
||||
- HEAD
|
||||
- OPTIONS
|
||||
- POST
|
||||
- PUT
|
||||
- PATCH
|
||||
- DELETE
|
||||
Loading…
x
Reference in New Issue
Block a user