| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80 | """Pfade, Schluessel und Grundwerte.
Schluessel liegen in /etc/kode/kode.env (nur root lesbar). Gemietete Karten
tragen das Label LABEL; aufgeraeumt wird nur, was dieses Label traegt - andere
Karten im selben Konto bleiben unberuehrt.
"""
import os
from pathlib import Path
KONFIG_DATEI = Path(os.getenv("KODE_KONFIG", "/etc/kode/kode.env"))
def _env_laden(pfad: Path) -> None:
try:
zeilen = pfad.read_text(encoding="utf-8").splitlines()
except OSError:
return
for zeile in zeilen:
zeile = zeile.strip()
if not zeile or zeile.startswith("#") or "=" not in zeile:
continue
name, wert = zeile.split("=", 1)
os.environ.setdefault(name.strip(), wert.strip().strip('"').strip("'"))
_env_laden(KONFIG_DATEI)
RUNPOD_API_KEY = os.getenv("RUNPOD_API_KEY", "").strip()
VAST_API_KEY = os.getenv("VAST_API_KEY", "").strip()
LABEL = os.getenv("KODE_LABEL", "kode-cli")
KENNUNG = "kode-cli/1.0 (+https://kode.boothtml.com)"
# Gemeinsamer Zustand fuer CLI und Waechter (eine Karte fuer alle Sitzungen).
DATEN = Path(os.getenv("KODE_DATEN", "/var/lib/kode"))
ZUSTAND = DATEN / "zustand.json"
SPERRE = DATEN / "zustand.lock"
DATENBANK = DATEN / "kode.db"
WAECHTER_LOG = DATEN / "waechter.log"
# Je Nutzer: Verlauf, Freigaben, Sicherungen. Bewusst nicht im Projektordner -
# eine .bak neben dem Original landet sonst womoeglich in einem Webroot.
HEIM = Path(os.getenv("KODE_HEIM", str(Path.home() / ".kode")))
EINSTELLUNGEN = HEIM / "einstellungen.json"
FREIGABEN = HEIM / "freigaben.json"
SICHERUNGEN = HEIM / "sicherungen"
EINGABE_VERLAUF = HEIM / "eingabe_verlauf"
SITZUNGEN = HEIM / "sitzungen"
# Fester Ollama-Endpunkt statt Mietkarte, z. B. zum Testen gegen ein lokales
# Ollama. Dann wird nichts gemietet.
FESTE_BASIS = os.getenv("KODE_BASIS", "").rstrip("/")
LEERLAUF_MIN = int(os.getenv("KODE_LEERLAUF_MIN", "30"))
# Automatischer Start (Wunsch 16.10.2026): bestes Angebot bis zu diesem
# Stundenpreis, bei Fehlschlag das naechste - ohne Rueckfrage.
# 17.10.2026: "darf etwas teurer sein, wenn sie dafuer schnell bereit ist - bis 0,5 USD"
MAX_PREIS = float(os.getenv("KODE_MAX_PREIS", "0.5"))
# 17.10.2026: eigene Grenze fuer die grossen Modelle (80-GB-Karten) - fuer
# 0,50 USD/h gibt es die nicht. "Auto-Limit fuer 1,5 USD pro Stunde"
GROSS_AB_VRAM = 80
MAX_PREIS_GROSS = float(os.getenv("KODE_MAX_PREIS_GROSS", "1.5"))
MAX_VERSUCHE = int(os.getenv("KODE_MAX_VERSUCHE", "8"))
# Fristen beim Hochfahren (Sekunden), aus einem frueheren Projekt.
BOOT_FRIST = 180
VAST_BOOT_FRIST = 480
DIENST_FRIST = 600
# Ohne Lebenszeichen der startenden Sitzung raeumt der Waechter ab.
PULS_FRIST = 240
# Karten mit unserem Label, die kein Zustand kennt (Absturz zwischen Mieten
# und Eintragen), werden nach dieser Zeit geloescht.
WAISEN_FRIST = 600
def verzeichnisse() -> None:
for p in (DATEN, HEIM, SICHERUNGEN, SITZUNGEN):
p.mkdir(parents=True, exist_ok=True) |