bambuddy-slicer-cert.sh 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. #!/usr/bin/env bash
  2. # Bambuddy: let Bambu Studio and OrcaSlicer connect to this Bambuddy's virtual
  3. # printers.
  4. #
  5. # The slicers only talk to printers whose certificate is listed in their file
  6. # printer.cer. This script adds this Bambuddy's certificate to every
  7. # printer.cer it finds (Linux and macOS), keeps a copy of the original next to
  8. # it (printer.cer.before-bambuddy), and changes nothing where the certificate
  9. # is already there. Slicer updates and reinstalls replace printer.cer, so run
  10. # it again after one.
  11. #
  12. # bash bambuddy-slicer-cert.sh
  13. #
  14. # It asks for your password (sudo) when a slicer is installed system-wide.
  15. #
  16. # Made by Bambuddy for the certificate with SHA-256 fingerprint
  17. # __BAMBUDDY_CA_FINGERPRINT__
  18. set -u
  19. CA_PEM=$(
  20. cat <<'BAMBUDDY_CA_PEM'
  21. __BAMBUDDY_CA_PEM__
  22. BAMBUDDY_CA_PEM
  23. )
  24. # Only for Bambuddy's own tests: look for printer.cer under these directories
  25. # instead of the usual places, and never use sudo.
  26. TEST_ROOTS=${BAMBUDDY_CERT_TEST_ROOTS:-}
  27. # Started with "sudo bash ...": look in the home folder of whoever ran sudo,
  28. # not root's, for Flatpaks and extracted AppImages.
  29. if [ -z "$TEST_ROOTS" ] && [ "$(id -u)" -eq 0 ] && [ -n "${SUDO_USER:-}" ]; then
  30. case "$SUDO_USER" in
  31. *[!A-Za-z0-9._-]*) ;;
  32. *) HOME=$(eval echo "~$SUDO_USER") ;;
  33. esac
  34. fi
  35. # The base64 text of each certificate in a PEM file, one per line, without
  36. # line breaks: the same certificate gives the same line, however the file
  37. # wraps or ends its lines.
  38. cert_bodies() {
  39. tr -d '\r' <"$1" | awk '
  40. /-----BEGIN CERTIFICATE-----/ { inside = 1; body = ""; next }
  41. /-----END CERTIFICATE-----/ { if (inside) print body; inside = 0; next }
  42. inside { gsub(/[ \t]/, ""); body = body $0 }
  43. '
  44. }
  45. # The PEM text of certificate number $2 (from 1) in file $1.
  46. cert_block() {
  47. tr -d '\r' <"$1" | awk -v want="$2" '
  48. /-----BEGIN CERTIFICATE-----/ { n++ }
  49. n == want && !done { print }
  50. n == want && /-----END CERTIFICATE-----/ { done = 1 }
  51. '
  52. }
  53. cert_subject() {
  54. openssl x509 -noout -subject -nameopt RFC2253 2>/dev/null
  55. }
  56. find_cert_files() {
  57. if [ -n "$TEST_ROOTS" ]; then
  58. for root in $TEST_ROOTS; do
  59. find "$root" -type f -name printer.cer 2>/dev/null
  60. done
  61. return
  62. fi
  63. if [ "$(uname -s)" = "Darwin" ]; then
  64. for apps in /Applications "$HOME/Applications"; do
  65. [ -d "$apps" ] || continue
  66. find "$apps" -maxdepth 1 -iname '*.app' \( -iname '*bambu*' -o -iname '*orca*' \) 2>/dev/null |
  67. while IFS= read -r app; do
  68. [ -f "$app/Contents/Resources/cert/printer.cer" ] && printf '%s\n' "$app/Contents/Resources/cert/printer.cer"
  69. done
  70. done
  71. return
  72. fi
  73. # Packages (/usr, /opt), Flatpak (system and user), and AppImages
  74. # extracted into the home folder (squashfs-root).
  75. for root in /usr/share /usr/local/share /usr/lib /opt /var/lib/flatpak/app "$HOME/.local/share/flatpak/app"; do
  76. [ -d "$root" ] || continue
  77. # A Flatpak's file sits 10 levels down
  78. # (<id>/x86_64/stable/<commit>/files/share/<slicer>/resources/cert).
  79. find "$root" -maxdepth 12 -type f -name printer.cer \( -ipath '*bambu*' -o -ipath '*orca*' \) 2>/dev/null
  80. done
  81. # An extracted AppImage is a folder squashfs-root, wherever it was
  82. # extracted; it is a slicer's when it holds the slicer's program.
  83. find "$HOME" -maxdepth 5 -type d -name squashfs-root 2>/dev/null |
  84. while IFS= read -r extracted; do
  85. if [ -n "$(find "$extracted" -maxdepth 2 \( -iname '*bambu*' -o -iname '*orca*' \) 2>/dev/null | head -n 1)" ]; then
  86. find "$extracted" -maxdepth 6 -type f -name printer.cer 2>/dev/null
  87. fi
  88. done
  89. }
  90. file_mode() {
  91. stat -c '%a' "$1" 2>/dev/null || stat -f '%Lp' "$1" 2>/dev/null || echo 644
  92. }
  93. # Adds the certificate to one printer.cer. Prints one line saying what it did.
  94. # Returns 0 when the file has the certificate afterwards, 1 when it failed.
  95. update_file() {
  96. local file=$1 dir sudo="" ours count i block subject drop="" tmp mode
  97. dir=$(dirname "$file")
  98. ours=$(printf '%s\n' "$CA_PEM" | awk '/BEGIN/{i=1;next} /END/{i=0} i{gsub(/[ \t\r]/,"");b=b $0} END{print b}')
  99. if cert_bodies "$file" | grep -qxF "$ours"; then
  100. echo " already there: $file"
  101. return 0
  102. fi
  103. # A slicer uses the first certificate with the right name, so an earlier
  104. # issue of this CA (same name, same key, other dates) would hide this
  105. # one: it goes. Not the bare legacy name: older Bambuddy installs all
  106. # share it, and it may be another one's.
  107. if command -v openssl >/dev/null 2>&1; then
  108. subject=$(printf '%s\n' "$CA_PEM" | cert_subject)
  109. [ "$subject" = "subject=CN=Virtual Printer CA" ] && subject=""
  110. count=$(cert_bodies "$file" | wc -l | tr -d ' ')
  111. i=1
  112. while [ "$i" -le "$count" ]; do
  113. block=$(cert_block "$file" "$i")
  114. if [ -n "$subject" ] && [ "$(printf '%s\n' "$block" | cert_subject)" = "$subject" ]; then
  115. drop="$drop $i"
  116. fi
  117. i=$((i + 1))
  118. done
  119. fi
  120. if [ -z "$TEST_ROOTS" ] && [ ! -w "$dir" ]; then
  121. sudo="sudo"
  122. fi
  123. tmp=$(mktemp "${TMPDIR:-/tmp}/bambuddy-cert.XXXXXX") || return 1
  124. tr -d '\r' <"$file" | awk -v drop="$drop " '
  125. /-----BEGIN CERTIFICATE-----/ { n++; skip = index(drop, " " n " ") > 0 }
  126. !skip { print }
  127. skip && /-----END CERTIFICATE-----/ { skip = 0 }
  128. ' >"$tmp"
  129. printf '%s\n' "$CA_PEM" >>"$tmp"
  130. mode=$(file_mode "$file")
  131. # Written next to the file and then moved over it, so the slicer never
  132. # sees half a file, and a Flatpak's shared copy is not changed in place.
  133. if { [ -e "$file.before-bambuddy" ] || $sudo cp -p "$file" "$file.before-bambuddy"; } &&
  134. $sudo cp "$tmp" "$file.bambuddy-new" &&
  135. $sudo chmod "$mode" "$file.bambuddy-new" &&
  136. $sudo mv -f "$file.bambuddy-new" "$file"; then
  137. rm -f "$tmp"
  138. if [ -n "$drop" ]; then
  139. echo " replaced an older Bambuddy certificate: $file"
  140. else
  141. echo " added: $file"
  142. fi
  143. return 0
  144. fi
  145. rm -f "$tmp"
  146. $sudo rm -f "$file.bambuddy-new" 2>/dev/null
  147. echo " FAILED: $file"
  148. if [ "$(uname -s)" = "Darwin" ]; then
  149. echo " macOS may block changes to apps: allow your terminal under"
  150. echo " System Settings > Privacy & Security > App Management, then run this again."
  151. fi
  152. return 1
  153. }
  154. main() {
  155. local files failed=0 found=0 file
  156. echo "Bambuddy slicer certificate"
  157. echo
  158. files=$(find_cert_files | sort -u)
  159. if [ -n "$files" ]; then
  160. while IFS= read -r file; do
  161. found=1
  162. update_file "$file" || failed=1
  163. done <<EOF
  164. $files
  165. EOF
  166. fi
  167. if [ -z "$TEST_ROOTS" ] && [ "$(uname -s)" != "Darwin" ]; then
  168. appimages=$(find "$HOME" -maxdepth 3 -type f \( -iname '*bambu*.appimage' -o -iname '*orca*.appimage' \) 2>/dev/null)
  169. if [ -n "$appimages" ]; then
  170. echo
  171. echo "An AppImage can't be changed. To use one with Bambuddy, extract it once"
  172. echo "and start the slicer from the extracted copy:"
  173. echo " ./<slicer>.AppImage --appimage-extract (creates squashfs-root)"
  174. echo " bash $0 (run this script again)"
  175. echo " ./squashfs-root/AppRun (start the slicer like this from now on)"
  176. echo "Found: $(printf '%s' "$appimages" | tr '\n' ' ')"
  177. fi
  178. fi
  179. echo
  180. if [ "$found" -eq 0 ]; then
  181. echo "No Bambu Studio or OrcaSlicer installation found."
  182. echo "See the Bambuddy wiki (Virtual Printer > Certificate Installation) to add it by hand."
  183. return 2
  184. fi
  185. if [ "$failed" -ne 0 ]; then
  186. echo "Not every slicer could be updated, see above."
  187. return 1
  188. fi
  189. if [ -z "$TEST_ROOTS" ] && pgrep -if 'bambu.?studio|orca.?slicer' >/dev/null 2>&1; then
  190. echo "Done. A slicer is running: quit it completely and start it again."
  191. else
  192. echo "Done. The slicer uses the certificate the next time it starts."
  193. fi
  194. return 0
  195. }
  196. main "$@"