Browse Source

Port over AutoUpdateApplication.cake

This came from codesearch-gui
Macoy Madson 3 months ago
  1. 2
  2. 210


@ -94,6 +94,8 @@ Here are the known compatibility results, where blank means untested/unknown:
| Allocator.cake | Yes | Yes | Yes | Yes |
| Aubio.cake | Yes | | | |
| AutoTest.cake | Yes | Yes | Yes | Yes |
| AutoUpdate.cake | Yes | | Yes | |
| AutoUpdateApplication.cake | Yes | | Yes | |
| Compression.cake | Yes | | | |
| Config_ZigCompile.cake | Yes | | | |
| Config_ZigWindows.cake | Yes | | | |


@ -0,0 +1,210 @@
;; AutoUpdateApplication.cake: Use AutoUpdate.cake to update the entire application, i.e., update itself.
;; Define 'SelfUpdateFromLocalServer to disable authentication and use local server
;; Cakelisp
"CHelpers.cake" "RunProcess.cake"
;; GameLib
"Introspection.cake" "TaskSystem.cake" "AutoUpdate.cake" "OpenSSL.cake" "Curl.cake"
"DynamicArray.cake" "VersionedData.cake"
;; For time
(c-import "<stdlib.h>" "<string.h>") ;; malloc, memset
;; Stored separately from app data so that at startup the old version isn't confused by new version userdata
(def-introspect-struct auto-update-data
auto-update-executable ([] 2048 char)
auto-update-version int)
(var s-auto-update-version-header version-header (array 2))
(var s-auto-update-data auto-update-data (array 0))
(var s-new-file-to-use ([] 2048 char) (array 0)) ;; TODO hacky
;; TODO Make not global
(var-global g-is-self-updating bool false)
(var-global g-new-version int 0)
(var-global g-new-version-changelog (* char) null)
(var s-last-time-checked-for-update float 0.f)
(var s-self-update-throttle-interval-seconds (const float) 15.f)
(var-global g-self-update-load-error-string ([] 2048 char) (array 0))
(var-global g-self-update-save-error-string ([] 2048 char) (array 0))
(defenum self-update-status
(var-global g-self-update-status self-update-status self-update-status-none)
(var s-curl (* CURL) null)
;; zip machsearch
;; In gamelib/test:
;; ./cryptography-cli create-signed-file ../../codesearch-gui/ ~/Repositories/side-hustle/ProductUpdates/Machsearch/
(defstruct self-update-parameters
current-version int
output-directory (* (const char))
curl (* void) ;; May be null if self-update should handle CURL acquisition
;; Should be crypto_sign_PUBLICKEYBYTES in length
public-key (* (unsigned char))
update-cakedata-url (* (const char)))
(def-task try-to-auto-update (arguments (* self-update-parameters) result-out (* self-update-status))
(when g-new-version-changelog
(auto-update-changelog-free g-new-version-changelog)
(set g-new-version-changelog null))
(unless (auto-update-download (path arguments > curl)
(path arguments > public-key)
(path arguments > update-cakedata-url)
(path arguments > current-version)
(path arguments > output-directory)
s-new-file-to-use (sizeof s-new-file-to-use)
(addr g-new-version) (addr g-new-version-changelog))
(set (deref result-out) self-update-status-unable-to-retrieve-update)
(if (at 0 s-new-file-to-use)
(fprintf stderr "Use file %s\n" s-new-file-to-use)
(if g-new-version-changelog
(fprintf stderr "Changelog:\n%s\n" g-new-version-changelog))
(set (deref result-out) self-update-status-waiting-to-accept-update))
(fprintf stderr "Up-to-date\n")
(set (deref result-out) self-update-status-up-to-date))))
(def-task auto-update-finished ()
(set g-is-self-updating false))
;; NOTE: These arguments need to persist until g-is-self-updating = false
(defun self-update (arguments (* self-update-parameters))
(when (self-update-is-throttling)
(fprintf stderr "Self-update request ignored (throttling)\n")
(set s-last-time-checked-for-update (get-time-since-startup))
;; Automatically provision CURL if none was provided
;; Lazily initialize so we don't hit the network unless they actually ask
(unless (path arguments > curl)
(unless s-curl
(when (!= (curl_global_init CURL_GLOBAL_DEFAULT) 0)
(fprintf stderr "error: Failed to initialize curl\n")
(set s-curl (curl_easy_init))
;; For testing only! Disable SSL certificate validation
(curl_easy_setopt s-curl CURLOPT_SSL_VERIFYPEER 0)
(curl_easy_setopt s-curl CURLOPT_SSL_VERIFYHOST 0))))
(unless s-curl
(fprintf stderr "error: Failed to get curl\n")
(set (path arguments > curl) s-curl))
(try-to-auto-update arguments (addr g-self-update-status))
(auto-update-finished :pin-to-main-thread))
(set g-is-self-updating true))
(defun-local self-update-save-config (filename (* (const char)))
(var read-version version-header (array 0))
(var result versioned-data-result
(save-versioned-data filename
(addr s-auto-update-version-header)
(addr s-auto-update-data)
(sizeof g-self-update-save-error-string))))
;; User says they want to run the new version
(defun self-update-confirm (filename (* (const char)))
(strcpy (field s-auto-update-data auto-update-executable) s-new-file-to-use)
(set (field s-auto-update-data auto-update-version) g-new-version)
(set g-self-update-status self-update-status-request-restart-to-new-version)
(self-update-save-config filename))
;; Make sure the user doesn't kill my server
(defun self-update-is-throttling (&return bool)
(unless s-last-time-checked-for-update (return false))
(var current-time float (get-time-since-startup))
(return (< (- current-time s-last-time-checked-for-update) s-self-update-throttle-interval-seconds)))
(c-import "<sys/stat.h>")))
(defun self-update-launch-new-version ()
(fprintf stderr "Launching %s\n" s-new-file-to-use)
;; This is gonna be the weird thing security-wise
(when (= -1 (chmod s-new-file-to-use (bit-or S_IRUSR S_IWUSR S_IXUSR S_IRGRP S_IXGRP S_IROTH S_IXOTH)))
(perror "self-update-launch-new-version (adding executable permission to update file): ")
(var arguments RunProcessArguments (array 0))
(set (field arguments fileToExecute) s-new-file-to-use)
(set (field arguments workingDirectory) null) ;; working dir same as parent
(var final-command (* (* char)) null) ;; dynarray
(dynarray-set-length final-command 2)
(set (at 0 final-command) s-new-file-to-use)
(set (at 1 final-command) null)
(set (field arguments arguments) (type-cast final-command (* (* (const char)))))
(var-static status int 0)
(unless (= 0 (runProcess arguments (addr status)))
;; TODO Output to user
(fprintf stderr "Failed to start update executable\n"))
;; We wait for process so we can continue to forward its output. If we don't do this, we'll cause
;; SIGPIPE when the child process tries to output to stderr etc., because runProcess() hooks up
;; our pipes. We can wait for it to finish.
;; TODO: We could downgrade the version if this new one crashes quickly
(waitForAllProcessesClosed null)
(dynarray-free final-command))
(def-function-signature-global self-update-execute-function (&return int))
(defun self-update-launch-new-version-or-execute (current-version int
current-version-func self-update-execute-function)
(var should-run-new-version bool
(< current-version (field s-auto-update-data auto-update-version)))
(when should-run-new-version
(set g-self-update-status self-update-status-request-restart-to-new-version)
(strcpy s-new-file-to-use (field s-auto-update-data auto-update-executable)))
;; Note: this will handle saving the userdata if necessary
(unless should-run-new-version
;; Update should-run in case we just updated
(set should-run-new-version (< current-version (field s-auto-update-data auto-update-version)))
(when (and should-run-new-version
(= g-self-update-status self-update-status-request-restart-to-new-version))
(defun self-update-shutdown ()
(when s-curl
(curl_easy_cleanup s-curl)
(when g-new-version-changelog
(auto-update-changelog-free g-new-version-changelog))
(free-introspect-struct-fields auto-update-data--metadata
(addr s-auto-update-data) free))
(defun self-update-load-config (filename (* (const char)))
(var read-version version-header (array 0))
(var load-result versioned-data-result
(load-versioned-data filename
(addr s-auto-update-version-header)
(addr read-version)
(addr s-auto-update-data)
(sizeof g-self-update-load-error-string))))