common.sh 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. #!/usr/bin/env bash
  2. # ============================================================================
  3. # Gemeinsame Funktionen fuer make_installer_*.sh
  4. # ============================================================================
  5. set -euo pipefail
  6. # Verzeichnisse
  7. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  8. SOURCE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
  9. EXCLUDE_FILE="${SCRIPT_DIR}/exclude_patterns.txt"
  10. INSTALLER_DIR="${SCRIPT_DIR}/installer"
  11. BUILD_CONF="${SCRIPT_DIR}/build.conf"
  12. BUILD_LOG="${SCRIPT_DIR}/build.log"
  13. # --- build.conf laden ---
  14. load_build_conf() {
  15. # Defaults
  16. INCLUDE_AUDIO_ASSETS=true
  17. INCLUDE_MUSIC_ASSETS=false
  18. INCLUDE_MORPHER_ASSETS=true
  19. INCLUDE_WAKEWORD_MODELS=true
  20. INCLUDE_PIPER_MODELS=true # models/piper/*.onnx (inkl. Custom-Voices)
  21. INCLUDE_SYMSPELL=true
  22. INCLUDE_LLM_MODELS=false
  23. INCLUDE_PLUGIN_MODELS=false
  24. INCLUDE_TRAINER_SOURCE=true
  25. INCLUDE_TRAINING_DATA=false
  26. INCLUDE_DOCS=true
  27. INCLUDE_BASH_SCRIPTS=true
  28. if [[ -f "${BUILD_CONF}" ]]; then
  29. # Nur KEY=value Zeilen laden (keine Kommentare, keine leeren Zeilen)
  30. while IFS='=' read -r key value; do
  31. key=$(echo "${key}" | xargs) # Whitespace trimmen
  32. value=$(echo "${value}" | xargs)
  33. [[ -z "${key}" || "${key}" == \#* ]] && continue
  34. declare -g "${key}=${value}"
  35. done < "${BUILD_CONF}"
  36. echo " build.conf geladen"
  37. else
  38. echo " WARNUNG: ${BUILD_CONF} nicht gefunden — verwende Defaults"
  39. fi
  40. }
  41. # --- Dynamische Excludes aus build.conf generieren ---
  42. build_conf_excludes() {
  43. local mode="$1"
  44. local excludes=""
  45. # Assets
  46. if [[ "${INCLUDE_AUDIO_ASSETS}" != "true" ]]; then
  47. excludes+=" assets/default/audio"
  48. fi
  49. if [[ "${INCLUDE_MUSIC_ASSETS}" != "true" ]]; then
  50. excludes+=" assets/default/music"
  51. fi
  52. if [[ "${INCLUDE_MORPHER_ASSETS}" != "true" ]]; then
  53. excludes+=" assets/default/audio_morpher"
  54. fi
  55. # Modelle
  56. if [[ "${INCLUDE_WAKEWORD_MODELS}" != "true" ]]; then
  57. excludes+=" models/wakeword/*/*.onnx"
  58. fi
  59. if [[ "${INCLUDE_PIPER_MODELS}" != "true" ]]; then
  60. excludes+=" models/piper/*.onnx models/piper/.cache"
  61. fi
  62. if [[ "${INCLUDE_SYMSPELL}" != "true" ]]; then
  63. excludes+=" models/symspell"
  64. fi
  65. if [[ "${INCLUDE_LLM_MODELS}" != "true" ]]; then
  66. excludes+=" models/nlp/*.gguf"
  67. fi
  68. if [[ "${INCLUDE_PLUGIN_MODELS}" != "true" ]]; then
  69. # Alle Plugin-Models ausschliessen
  70. # Piper-Voices werden ueber models/piper/ (INCLUDE_PIPER_MODELS) behalten
  71. excludes+=" plugins/*/models"
  72. fi
  73. # Trainer
  74. if [[ "${INCLUDE_TRAINER_SOURCE}" != "true" ]]; then
  75. excludes+=" trainer"
  76. fi
  77. if [[ "${INCLUDE_TRAINING_DATA}" != "true" ]]; then
  78. excludes+=" trainer/data/raw trainer/data/processed trainer/assets trainer/cache trainer/processed"
  79. fi
  80. # Sonstiges
  81. if [[ "${INCLUDE_DOCS}" != "true" ]]; then
  82. excludes+=" docs"
  83. fi
  84. if [[ "${INCLUDE_BASH_SCRIPTS}" != "true" ]]; then
  85. excludes+=" bash"
  86. fi
  87. echo "${excludes}"
  88. }
  89. # --- Version aus trixy_core/utils/version.py parsen ---
  90. parse_version() {
  91. local version_file="${SOURCE_DIR}/trixy_core/utils/version.py"
  92. if [[ ! -f "${version_file}" ]]; then
  93. echo "FEHLER: ${version_file} nicht gefunden" >&2
  94. exit 1
  95. fi
  96. local major minor revision
  97. major=$(grep -E '^MAJOR:\s*int\s*=' "${version_file}" | grep -oE '[0-9]+')
  98. minor=$(grep -E '^MINOR:\s*int\s*=' "${version_file}" | grep -oE '[0-9]+')
  99. revision=$(grep -E '^REVISION:\s*int\s*=' "${version_file}" | grep -oE '[0-9]+')
  100. echo "${major}.${minor}.${revision}"
  101. }
  102. # --- Git-Revision ermitteln (Kurz-Hash, oder "nogit") ---
  103. get_git_revision() {
  104. if command -v git >/dev/null 2>&1 && git -C "${SOURCE_DIR}" rev-parse --git-dir >/dev/null 2>&1; then
  105. git -C "${SOURCE_DIR}" rev-parse --short HEAD 2>/dev/null || echo "nogit"
  106. else
  107. echo "nogit"
  108. fi
  109. }
  110. # --- Git-Status pruefen: "clean" oder "dirty" ---
  111. get_git_status() {
  112. if command -v git >/dev/null 2>&1 && git -C "${SOURCE_DIR}" rev-parse --git-dir >/dev/null 2>&1; then
  113. # Cache- und Runtime-Dateien ignorieren bei der Pruefung
  114. local changes
  115. changes=$(git -C "${SOURCE_DIR}" status --porcelain 2>/dev/null \
  116. | grep -v '__pycache__' \
  117. | grep -v '\.pyc$' \
  118. | grep -v '^\s*M logs/' \
  119. | grep -v '^\s*M data/activity/' \
  120. | grep -v '^\s*M cron/' \
  121. || true)
  122. if [[ -z "${changes}" ]]; then
  123. echo "clean"
  124. else
  125. echo "dirty"
  126. fi
  127. else
  128. echo "nogit"
  129. fi
  130. }
  131. # --- Naechste Build-Nummer ermitteln ---
  132. # Wenn der aktuelle Commit-Hash bereits in der build.log steht, wird dieselbe
  133. # Build-Nummer zurueckgegeben. Sonst wird max(build_number) + 1 verwendet.
  134. get_next_build_number() {
  135. local git_rev="$1"
  136. if [[ ! -f "${BUILD_LOG}" ]]; then
  137. echo "1"
  138. return
  139. fi
  140. # Suche existierenden Eintrag mit gleichem git_rev (Spalte 4)
  141. local existing
  142. existing=$(awk -F',' -v rev="${git_rev}" '
  143. NR > 1 && $4 == rev { print $1; exit }
  144. ' "${BUILD_LOG}")
  145. if [[ -n "${existing}" ]]; then
  146. echo "${existing}"
  147. return
  148. fi
  149. # Neue Build-Nummer = max + 1
  150. local max_build
  151. max_build=$(awk -F',' '
  152. NR > 1 && $1 ~ /^[0-9]+$/ { if ($1 > m) m = $1 }
  153. END { print m + 0 }
  154. ' "${BUILD_LOG}")
  155. echo "$((max_build + 1))"
  156. }
  157. # --- build.log Eintrag hinzufuegen/aktualisieren ---
  158. # Spalten: build_number,version,full_version,git_rev,git_status,timestamp,modes
  159. #
  160. # Wenn git_rev bereits existiert, wird der Modus zur modes-Spalte hinzugefuegt
  161. # (falls noch nicht vorhanden) und timestamp aktualisiert.
  162. update_build_log() {
  163. local build_number="$1"
  164. local version="$2"
  165. local full_version="$3"
  166. local git_rev="$4"
  167. local git_status="$5"
  168. local mode="$6"
  169. local timestamp
  170. timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
  171. # Header erstellen wenn Datei neu
  172. if [[ ! -f "${BUILD_LOG}" ]]; then
  173. echo "build_number,version,full_version,git_rev,git_status,timestamp,modes" > "${BUILD_LOG}"
  174. fi
  175. # Existierenden Eintrag suchen
  176. if grep -q "^${build_number}," "${BUILD_LOG}" 2>/dev/null; then
  177. # Eintrag aktualisieren: modes ergaenzen, timestamp updaten
  178. local tmp
  179. tmp=$(mktemp)
  180. awk -F',' -v bn="${build_number}" -v mode="${mode}" -v ts="${timestamp}" -v gs="${git_status}" '
  181. BEGIN { OFS = "," }
  182. NR == 1 { print; next }
  183. $1 == bn {
  184. # Modes zusammenfuehren (ohne Duplikate)
  185. n = split($7, arr, "|")
  186. found = 0
  187. for (i = 1; i <= n; i++) if (arr[i] == mode) found = 1
  188. if (!found) {
  189. if ($7 == "") $7 = mode
  190. else $7 = $7 "|" mode
  191. }
  192. $5 = gs # status aktualisieren
  193. $6 = ts # timestamp aktualisieren
  194. print
  195. next
  196. }
  197. { print }
  198. ' "${BUILD_LOG}" > "${tmp}"
  199. mv "${tmp}" "${BUILD_LOG}"
  200. else
  201. # Neuer Eintrag
  202. echo "${build_number},${version},${full_version},${git_rev},${git_status},${timestamp},${mode}" >> "${BUILD_LOG}"
  203. fi
  204. }
  205. # --- VERSION-Datei fuer den Build generieren ---
  206. # Wird ins Release-Verzeichnis geschrieben UND ins Source-Tar aufgenommen.
  207. generate_version_file() {
  208. local target_path="$1"
  209. local mode="$2"
  210. local version="$3"
  211. local build_number="$4"
  212. local full_version="$5"
  213. local git_rev="$6"
  214. local git_status="$7"
  215. local timestamp
  216. timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
  217. cat > "${target_path}" << EOF
  218. Name: Trixy
  219. Mode: ${mode}
  220. Version: ${version}
  221. Build: ${build_number}
  222. FullVersion: ${full_version}
  223. GitRevision: ${git_rev}
  224. GitStatus: ${git_status}
  225. BuildDate: ${timestamp}
  226. EOF
  227. }
  228. # --- Build-Info sammeln und ausgeben ---
  229. # Setzt globale Variablen: BUILD_VERSION, BUILD_NUMBER, BUILD_FULL_VERSION,
  230. # BUILD_GIT_REV, BUILD_GIT_STATUS
  231. collect_build_info() {
  232. BUILD_VERSION=$(parse_version)
  233. BUILD_GIT_REV=$(get_git_revision)
  234. BUILD_GIT_STATUS=$(get_git_status)
  235. BUILD_NUMBER=$(get_next_build_number "${BUILD_GIT_REV}")
  236. BUILD_FULL_VERSION="${BUILD_VERSION}.${BUILD_NUMBER}"
  237. echo " Version: ${BUILD_VERSION}"
  238. echo " Build: ${BUILD_NUMBER}"
  239. echo " FullVersion: ${BUILD_FULL_VERSION}"
  240. echo " Git-Revision: ${BUILD_GIT_REV} (${BUILD_GIT_STATUS})"
  241. }
  242. # --- Release-Verzeichnis erstellen ---
  243. prepare_release_dir() {
  244. local version="$1"
  245. local mode="$2"
  246. local release_dir="${SCRIPT_DIR}/Release/trixy-${mode}-${version}"
  247. mkdir -p "${release_dir}"
  248. echo "${release_dir}"
  249. }
  250. # --- build.conf Einstellungen anzeigen ---
  251. print_build_conf() {
  252. local mode="$1"
  253. echo " Build-Konfiguration:"
  254. echo " Audio-Assets: ${INCLUDE_AUDIO_ASSETS}"
  255. echo " Musik-Assets: ${INCLUDE_MUSIC_ASSETS}"
  256. echo " Morpher-Assets: ${INCLUDE_MORPHER_ASSETS}"
  257. echo " Wakeword-Modelle: ${INCLUDE_WAKEWORD_MODELS}"
  258. echo " Piper-Modelle: ${INCLUDE_PIPER_MODELS}"
  259. echo " SymSpell: ${INCLUDE_SYMSPELL}"
  260. echo " LLM-Modelle: ${INCLUDE_LLM_MODELS}"
  261. echo " Plugin-Modelle: ${INCLUDE_PLUGIN_MODELS}"
  262. echo " Trainer-Source: ${INCLUDE_TRAINER_SOURCE}"
  263. echo " Trainings-Daten: ${INCLUDE_TRAINING_DATA}"
  264. echo " Dokumentation: ${INCLUDE_DOCS}"
  265. echo " Bash-Skripte: ${INCLUDE_BASH_SCRIPTS}"
  266. echo ""
  267. }
  268. # --- Source-tar erstellen ---
  269. create_source_tar() {
  270. local mode="$1"
  271. local version="$2"
  272. local release_dir="$3"
  273. local extra_excludes="$4" # Zusaetzliche Exclude-Patterns (Leerzeichen-getrennt)
  274. local tar_name="trixy-${mode}-${version}.tar.gz"
  275. local tar_path="${release_dir}/${tar_name}"
  276. echo " Erstelle ${tar_name}..."
  277. # VERSION-Datei temporaer in SOURCE_DIR schreiben (wird mit ins tar aufgenommen)
  278. local version_file="${SOURCE_DIR}/VERSION"
  279. generate_version_file "${version_file}" \
  280. "${mode}" \
  281. "${BUILD_VERSION}" \
  282. "${BUILD_NUMBER}" \
  283. "${BUILD_FULL_VERSION}" \
  284. "${BUILD_GIT_REV}" \
  285. "${BUILD_GIT_STATUS}"
  286. # Kopie ins Release-Verzeichnis
  287. cp "${version_file}" "${release_dir}/VERSION"
  288. # Basis-Excludes aus exclude_patterns.txt
  289. local exclude_opts=()
  290. while IFS= read -r pattern; do
  291. [[ -z "${pattern}" || "${pattern}" == \#* ]] && continue
  292. exclude_opts+=("--exclude=${pattern}")
  293. done < "${EXCLUDE_FILE}"
  294. # build.conf-basierte Excludes
  295. local conf_excludes
  296. conf_excludes=$(build_conf_excludes "${mode}")
  297. for pattern in ${conf_excludes}; do
  298. exclude_opts+=("--exclude=${pattern}")
  299. done
  300. # Modus-spezifische Excludes
  301. for pattern in ${extra_excludes}; do
  302. exclude_opts+=("--exclude=${pattern}")
  303. done
  304. # tar in temporaere Datei schreiben, dann verschieben
  305. local tmp_tar
  306. tmp_tar="$(mktemp)"
  307. local prefix="trixy-${mode}-${version}"
  308. tar czf "${tmp_tar}" \
  309. -C "${SOURCE_DIR}" \
  310. "${exclude_opts[@]}" \
  311. --exclude='make_installer/Release' \
  312. --transform="s,^\\.,${prefix}," \
  313. .
  314. mv "${tmp_tar}" "${tar_path}"
  315. # VERSION-Datei aus SOURCE_DIR wieder entfernen
  316. rm -f "${version_file}"
  317. echo " -> ${tar_name} ($(du -h "${tar_path}" | cut -f1))"
  318. }
  319. # --- Installer-tar erstellen ---
  320. create_installer_tar() {
  321. local mode="$1"
  322. local version="$2"
  323. local release_dir="$3"
  324. local tar_name="trixy-installer-${mode}-${version}.tar.gz"
  325. local tar_path="${release_dir}/${tar_name}"
  326. echo " Erstelle ${tar_name}..."
  327. tar czf "${tar_path}" \
  328. -C "${INSTALLER_DIR}/.." \
  329. --transform='s,^installer,trixy_installer,' \
  330. --exclude='__pycache__' \
  331. --exclude='*.pyc' \
  332. installer/
  333. echo " -> ${tar_name} ($(du -h "${tar_path}" | cut -f1))"
  334. }
  335. # --- install.sh generieren ---
  336. generate_install_sh() {
  337. local mode="$1"
  338. local version="$2"
  339. local release_dir="$3"
  340. local install_sh="${release_dir}/install.sh"
  341. local source_tar="trixy-${mode}-${version}.tar.gz"
  342. local installer_tar="trixy-installer-${mode}-${version}.tar.gz"
  343. echo " Generiere install.sh..."
  344. cat > "${install_sh}" << 'INSTALL_HEADER'
  345. #!/usr/bin/env bash
  346. # ============================================================================
  347. # Trixy Installer — Bootstrap-Script
  348. #
  349. # Entpackt den Installer, erstellt ein temporaeres venv und startet die TUI.
  350. # ============================================================================
  351. set -euo pipefail
  352. INSTALL_HEADER
  353. cat >> "${install_sh}" << INSTALL_VARS
  354. # --- Eingebettete Werte ---
  355. MODE="${mode}"
  356. VERSION="${version}"
  357. SOURCE_TAR="${source_tar}"
  358. INSTALLER_TAR="${installer_tar}"
  359. INSTALL_VARS
  360. cat >> "${install_sh}" << 'INSTALL_BODY'
  361. # --- Farben ---
  362. RED='\033[0;31m'
  363. GREEN='\033[0;32m'
  364. YELLOW='\033[1;33m'
  365. NC='\033[0m'
  366. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  367. TEMP_DIR=""
  368. cleanup() {
  369. if [[ -n "${TEMP_DIR}" && -d "${TEMP_DIR}" ]]; then
  370. echo -e "${YELLOW}Raeume auf...${NC}"
  371. rm -rf "${TEMP_DIR}"
  372. fi
  373. }
  374. trap cleanup EXIT
  375. echo ""
  376. echo -e "${GREEN}========================================"
  377. echo " Trixy Installer v${VERSION} [${MODE}]"
  378. echo -e "========================================${NC}"
  379. echo ""
  380. # 1. Pruefe ob tar-Dateien vorhanden
  381. if [[ ! -f "${SCRIPT_DIR}/${SOURCE_TAR}" ]]; then
  382. echo -e "${RED}FEHLER: ${SOURCE_TAR} nicht gefunden${NC}"
  383. exit 1
  384. fi
  385. if [[ ! -f "${SCRIPT_DIR}/${INSTALLER_TAR}" ]]; then
  386. echo -e "${RED}FEHLER: ${INSTALLER_TAR} nicht gefunden${NC}"
  387. exit 1
  388. fi
  389. # 2. Python >= 3.10 sicherstellen
  390. echo "Pruefe Python-Version..."
  391. PYTHON_BIN=""
  392. for py in python3.12 python3.11 python3.10 python3; do
  393. if command -v "${py}" >/dev/null 2>&1; then
  394. py_minor=$("${py}" -c "import sys; print(sys.version_info.minor)")
  395. if [[ "${py_minor}" -ge 10 ]]; then
  396. PYTHON_BIN="${py}"
  397. break
  398. fi
  399. fi
  400. done
  401. if [[ -z "${PYTHON_BIN}" ]]; then
  402. echo -e "${YELLOW}Python >= 3.10 nicht gefunden. Versuche Installation...${NC}"
  403. if command -v apt-get >/dev/null 2>&1; then
  404. sudo apt-get update -qq
  405. sudo apt-get install -y -qq python3 python3-venv python3-pip
  406. PYTHON_BIN="python3"
  407. else
  408. echo -e "${RED}FEHLER: Python >= 3.10 wird benoetigt${NC}"
  409. exit 1
  410. fi
  411. fi
  412. echo " Verwende: ${PYTHON_BIN} ($(${PYTHON_BIN} --version 2>&1))"
  413. # 3. Installer-tar in temp-Verzeichnis entpacken
  414. TEMP_DIR="$(mktemp -d)"
  415. echo "Entpacke Installer nach ${TEMP_DIR}..."
  416. tar xzf "${SCRIPT_DIR}/${INSTALLER_TAR}" -C "${TEMP_DIR}"
  417. # 4. Installer-venv erstellen
  418. INST_VENV="${TEMP_DIR}/installer-venv"
  419. echo "Erstelle Installer-venv..."
  420. "${PYTHON_BIN}" -m venv "${INST_VENV}"
  421. "${INST_VENV}/bin/pip" install --upgrade pip -q 2>/dev/null
  422. "${INST_VENV}/bin/pip" install -r "${TEMP_DIR}/trixy_installer/requirements.txt" -q
  423. # 5. TUI starten
  424. echo ""
  425. echo -e "${GREEN}Starte Installer-TUI...${NC}"
  426. echo ""
  427. PYTHONPATH="${TEMP_DIR}" "${INST_VENV}/bin/python" -m trixy_installer \
  428. --mode "${MODE}" \
  429. --version "${VERSION}" \
  430. --source-archive "${SCRIPT_DIR}/${SOURCE_TAR}"
  431. echo ""
  432. echo -e "${GREEN}Installer beendet.${NC}"
  433. INSTALL_BODY
  434. chmod +x "${install_sh}"
  435. echo " -> install.sh generiert"
  436. }
  437. # --- Build zusammenfassen ---
  438. print_summary() {
  439. local mode="$1"
  440. local version="$2"
  441. local release_dir="$3"
  442. echo ""
  443. echo "========================================"
  444. echo " Build abgeschlossen: ${mode} v${BUILD_FULL_VERSION:-${version}}"
  445. echo "========================================"
  446. echo ""
  447. echo " Version: ${BUILD_VERSION:-${version}}"
  448. echo " Build: ${BUILD_NUMBER:-?}"
  449. echo " FullVersion: ${BUILD_FULL_VERSION:-${version}}"
  450. echo " Git: ${BUILD_GIT_REV:-unknown} (${BUILD_GIT_STATUS:-unknown})"
  451. echo ""
  452. echo " Release-Verzeichnis: ${release_dir}"
  453. echo ""
  454. ls -lh "${release_dir}/"
  455. echo ""
  456. echo " Deployment:"
  457. echo " 1. Release-Verzeichnis auf Zielgeraet kopieren"
  458. echo " 2. chmod +x install.sh"
  459. echo " 3. ./install.sh"
  460. echo ""
  461. }