#!/usr/bin/env bash # KidCAD update (V1.2.3) for Linux and Mac. Finds your KidCAD folder and the newest kidcad*.zip by itself. # Your family's things (the data folder), the AI/figure environments and your own printer profiles are kept. # Do NOT run this while a USB print is running: KidCAD restarts at the end and that stops a USB print. set -uo pipefail say() { printf '\n\033[1;35m==> %s\033[0m\n' "$*"; } warn() { printf '\033[1;33m[!] %s\033[0m\n' "$*"; } die() { printf '\033[1;31m[x] %s\033[0m\n' "$*"; exit 1; } # ---- 1. the newest KidCAD zip for this computer (Downloads, home, Desktop) if [[ "$(uname -s)" == "Darwin" ]]; then OTHER='(windows|linux)'; INSTALLER=install_mac.sh; else OTHER='(windows|mac)'; INSTALLER=install.sh; fi ZIP="" while IFS= read -r z; do # newest first if [[ -z "$ZIP" || "$z" -nt "$ZIP" ]]; then ZIP="$z"; fi done < <(find "$HOME/Downloads" "$HOME" "$HOME/Desktop" -maxdepth 2 -iname 'kidcad*.zip' -type f 2>/dev/null | grep -viE "_${OTHER}\.zip$" | grep -viE '_source\.zip$') [[ -n "$ZIP" ]] || die "No kidcad*.zip found in Downloads, Desktop or your home folder." say "Found this download: $ZIP ($(date -r "$ZIP" '+%b %d %H:%M'), $(du -h "$ZIP" | cut -f1))" read -r -p "Install this one? Only say yes if you downloaded it yourself from kidcad.xyz or GitHub. (y/N): " okzip [[ "$okzip" =~ ^[Yy] ]] || die "Nothing changed." # ---- 2. the installed KidCAD folder (the one with app/server.py and its .venv) DIRS=() while IFS= read -r d; do DIRS+=("$d"); done < <(find "$HOME" -maxdepth 5 \( -name Library -o -name .Trash -o -name .cache \ -o -name .venv -o -name 'kidcad_update_tmp*' -o -name node_modules \) -prune -o -path '*/app/server.py' -print 2>/dev/null \ | sed 's#/app/server.py$##' \ | while read -r d; do [[ -x "$d/.venv/bin/python" ]] && echo "$d"; done) if [[ ${#DIRS[@]} -eq 0 ]]; then die "Couldn't find an installed KidCAD folder (one with app/server.py and .venv)."; fi if [[ ${#DIRS[@]} -gt 1 ]]; then echo "More than one KidCAD folder found:" for i in "${!DIRS[@]}"; do echo " $((i + 1))) ${DIRS[$i]}"; done read -r -p "Which one should be updated? (number): " n [[ "$n" =~ ^[0-9]+$ && $n -ge 1 && $n -le ${#DIRS[@]} ]] || die "No folder picked. Nothing changed." K="${DIRS[$((n - 1))]}" else K="${DIRS[0]}" fi say "Updating KidCAD in: $K" # ---- 3. never during a USB print read -r -p "Is the printer printing over USB right now? (y/N): " busy if [[ "$busy" =~ ^[Yy] ]]; then die "Run this again after the print is done. Nothing changed."; fi # ---- 4. unpack and check the download TMP="$HOME/kidcad_update_tmp_$$" rm -rf "$TMP"; mkdir -p "$TMP" unzip -q "$ZIP" -d "$TMP" || die "The zip could not be opened. Download it again." SRC=$(dirname "$(find "$TMP" -maxdepth 3 -path '*/app/server.py' | head -1)") SRC=$(dirname "$SRC") [[ -f "$SRC/app/server.py" && -f "$SRC/run.sh" ]] || { rm -rf "$TMP"; die "That zip doesn't look like KidCAD."; } echo "New version: $(cat "$SRC/VERSION" 2>/dev/null || echo '?') Installed: $(cat "$K/VERSION" 2>/dev/null || echo 'before V1')" # ---- 5. safety copy of the program and the family's data (only the newest copy is kept; the AI programs are # not copied, they can be installed again) BK="$HOME/kidcad_backups"; STAMP=$(date +%Y%m%d_%H%M%S) mkdir -p "$BK" FREE_GB=$(df -Pk "$HOME" | awk 'NR==2 {print int($4 / 1048576)}') if [[ "${FREE_GB:-0}" -lt 10 ]]; then warn "Less than 10 GB free on the disk (${FREE_GB} GB): no safety copy this time. Your last safety copy in $BK is kept." warn "To free space: KidCAD -> Grown-ups -> Storage -> Clean up now." else say "Safety copy -> $BK" ok=1 tar -czf "$BK/program_$STAMP.tar.gz" -C "$K" --exclude=./data --exclude=./.venv --exclude=./engines/.venv-figures \ --exclude=./engines/Hunyuan3D-2 --exclude=./engines/.venv-triposg --exclude=./engines/TripoSG \ --exclude='*/__pycache__' . && echo "program saved" || ok=0 if [[ -d "$K/data" ]]; then tar -czf "$BK/data_$STAMP.tar.gz" -C "$K" --exclude=./data/tmp --exclude='*.gcode' data && echo "family data saved" || ok=0 fi if [[ $ok -eq 1 ]]; then # keep only this newest safety copy find "$BK" -maxdepth 1 -type f \( -name 'program_*.tar.gz' -o -name 'data_*.tar.gz' \) \ ! -name "program_$STAMP.tar.gz" ! -name "data_$STAMP.tar.gz" -delete echo "older safety copies removed (the newest one is kept)" else warn "The safety copy had a problem, so the older safety copies were kept." fi fi # ---- 6. put the new program in place (data, environments and your printer edits are kept) say "Copying the new KidCAD files" if command -v rsync >/dev/null; then rsync -a --exclude '/data' --exclude '/.venv' --exclude '/engines/.venv-figures' --exclude '/engines/Hunyuan3D-2' \ --exclude '/app/printers/' --exclude '__pycache__' "$SRC/" "$K/" else (cd "$SRC" && find . -type f -not -path './data/*' -not -path './app/printers/*' -not -path '*/__pycache__/*' \ -exec install -D -m 644 {} "$K/{}" \;) fi mkdir -p "$K/app/printers" cp -n "$SRC"/app/printers/*.json "$K/app/printers/" 2>/dev/null # new printers added, your old ones untouched chmod +x "$K"/*.sh rm -rf "$TMP" # ---- 7. Python packages say "Python packages" "$K/.venv/bin/python" -m pip install -q --upgrade -r "$K/requirements.txt" || warn "pip had a problem (see above)." if [[ -x "$K/engines/.venv-figures/bin/python" ]]; then say "Figure maker packages (keeps the versions that work)" "$K/engines/.venv-figures/bin/python" -m pip install -q "transformers<5" "huggingface-hub<1.0" diffusers \ || warn "Figure maker packages had a problem; drawings and AI builds still work." fi # ---- 7b. optional: the TripoSG figure engine (MIT license, about 8 GB of graphics memory) if command -v nvidia-smi >/dev/null && [[ ! -d "$K/engines/.venv-triposg" ]]; then read -r -p "Add the TripoSG figure engine now? It downloads several GB and takes a while. (y/N): " tsg if [[ "$tsg" =~ ^[Yy] ]]; then (cd "$K" && "./$INSTALLER" --triposg) || warn "TripoSG didn't install; the other figure models still work."; fi fi # ---- 8. check-up and restart say "Check-up" (cd "$K" && .venv/bin/python -m app.doctor) || true say "Restarting KidCAD" mkdir -p "$K/data/logs" cd "$K" if command -v setsid >/dev/null; then setsid nohup ./run.sh --restart >"$K/data/logs/restart.log" 2>&1 < /dev/null & else nohup ./run.sh --restart >"$K/data/logs/restart.log" 2>&1 < /dev/null & fi disown 2>/dev/null || true sleep 6 if curl -s --max-time 5 "http://127.0.0.1:${KIDCAD_PORT:-7860}/api/auth" >/dev/null; then say "Done! KidCAD $(cat "$K/VERSION") is running: http://localhost:${KIDCAD_PORT:-7860}" else warn "KidCAD didn't answer yet. Give it a few seconds, or start it from the desktop icon." echo "If it still won't start, the old program is in $BK/program_$STAMP.tar.gz" fi