2 changed files with 212 additions and 0 deletions
@ -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 |
|||
(import |
|||
;; Cakelisp |
|||
"CHelpers.cake" "RunProcess.cake" |
|||
;; GameLib |
|||
"Introspection.cake" "TaskSystem.cake" "AutoUpdate.cake" "OpenSSL.cake" "Curl.cake" |
|||
"DynamicArray.cake" "VersionedData.cake" |
|||
;; For time |
|||
"SDL.cake") |
|||
|
|||
(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 |
|||
self-update-status-none |
|||
self-update-status-unable-to-retrieve-update |
|||
self-update-status-up-to-date |
|||
self-update-status-waiting-to-accept-update |
|||
self-update-status-request-restart-to-new-version) |
|||
|
|||
(var-global g-self-update-status self-update-status self-update-status-none) |
|||
|
|||
(var s-curl (* CURL) null) |
|||
|
|||
;; zip machsearch.zip machsearch |
|||
;; In gamelib/test: |
|||
;; ./cryptography-cli create-signed-file ../../codesearch-gui/machsearch.zip ~/Repositories/side-hustle/ProductUpdates/Machsearch/Machsearch_Linux-x64.auto-update |
|||
|
|||
(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) |
|||
(return)) |
|||
(if (at 0 s-new-file-to-use) |
|||
(scope |
|||
(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)) |
|||
(scope |
|||
(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") |
|||
(return)) |
|||
(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") |
|||
(return)) |
|||
|
|||
(set s-curl (curl_easy_init)) |
|||
;; For testing only! Disable SSL certificate validation |
|||
(comptime-cond |
|||
('SelfUpdateFromLocalServer |
|||
(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") |
|||
(curl_global_cleanup) |
|||
(return)) |
|||
|
|||
(set (path arguments > curl) s-curl)) |
|||
|
|||
(task-system-execute |
|||
(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) |
|||
auto-update-data--metadata |
|||
(addr s-auto-update-data) |
|||
g-self-update-save-error-string |
|||
(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))) |
|||
|
|||
(comptime-cond |
|||
('Unix |
|||
(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 |
|||
(comptime-cond |
|||
('Unix |
|||
(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): ") |
|||
(return)))) |
|||
(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 |
|||
(current-version-func)) |
|||
|
|||
;; 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)) |
|||
(self-update-launch-new-version))) |
|||
|
|||
(defun self-update-shutdown () |
|||
(when s-curl |
|||
(curl_easy_cleanup s-curl) |
|||
(curl_global_cleanup)) |
|||
(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) |
|||
auto-update-data--metadata |
|||
(addr s-auto-update-data) |
|||
g-self-update-load-error-string |
|||
(sizeof g-self-update-load-error-string)))) |
Loading…
Reference in new issue