diff --git a/.gitignore b/.gitignore index 0ee0d1c..66337f2 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ include/ lib/ local/ share/ + +# custom +version.txt diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 42b003e..293a443 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,12 +10,12 @@ build: - platformio update script: - platformio ci --project-conf platformio.ini --board=lolin_d32_pro --build-dir build --keep-build-dir -# - cp ~/.platformio/packages/framework-arduinoespressif32/tools/sdk/bin/bootloader_dio_40m.bin build/.pio/build/lolin_d32_pro/ -# - cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/default.bin build/.pio/build/lolin_d32_pro/ +# - date +%s > build/.pio/build/lolin_d32_pro/build.version variables: {PLATFORMIO_CI_SRC: "src"} artifacts: paths: - build/.pio/build/lolin_d32_pro/*.bin + - version.txt deploy: stage: deploy @@ -31,6 +31,7 @@ deploy: - chmod 644 ~/.ssh/known_hosts script: - scp build/.pio/build/lolin_d32_pro/firmware.bin "${SSH_USER_HOST}:${DEPLOY_PATH}" + - scp version.txt "${SSH_USER_HOST}:${DEPLOY_PATH}".version dependencies: - build when: manual diff --git a/platformio.ini b/platformio.ini index 077ba2a..c19f3b5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -21,3 +21,5 @@ build_flags = ; ArduinoJSON monitor_speed = 115200 + +extra_scripts = pre:script/autoversioning.py diff --git a/script/autoversioning.py b/script/autoversioning.py new file mode 100755 index 0000000..187d317 --- /dev/null +++ b/script/autoversioning.py @@ -0,0 +1,13 @@ +Import("env") + +import time + +ver = time.time() + +f = open("version.txt", "w") +f.write(str(ver)) +f.close() + +env.Append(CPPDEFINES=[ + ("FW_VERSION", ver) +]) diff --git a/src/XD0OTA.cpp b/src/XD0OTA.cpp new file mode 100644 index 0000000..1d9f3f0 --- /dev/null +++ b/src/XD0OTA.cpp @@ -0,0 +1,132 @@ +#include "XD0OTA.h" + +#include +#include + +#include +#include + +#include + +static const char* TAG = "OTA"; + +XD0OTA::XD0OTA(String deviceName) : deviceName{deviceName} {} + +// Set time via NTP, as required for x.509 validation +void XD0OTA::setClock() { + configTime(0, 0, "pool.ntp.org", "time.nist.gov"); // UTC + + Serial.print(F("Waiting for NTP time sync: ")); + time_t now = time(nullptr); + while (now < 8 * 3600 * 2) { + yield(); + delay(500); + Serial.print(F(".")); + now = time(nullptr); + } + + Serial.println(F("")); + struct tm timeinfo; + gmtime_r(&now, &timeinfo); + Serial.print(F("Current time: ")); + Serial.print(asctime(&timeinfo)); +} + +String XD0OTA::getMAC() { + uint8_t mac[6]; + char result[14]; + + WiFi.macAddress( mac ); + snprintf( result, sizeof( result ), "%02x%02x%02x%02x%02x%02x", mac[ 0 ], mac[ 1 ], mac[ 2 ], mac[ 3 ], mac[ 4 ], mac[ 5 ] ); + + return String( result ); +} + +String XD0OTA::getUpdateURL(String file, String extension) { + String updateURL = String(fwUrlBase); + updateURL.concat(file); + updateURL.concat(extension); + return updateURL; +} + +void XD0OTA::update(void) { + setClock(); + + // try device specific image first + String fwURL = getUpdateURL(getMAC(), ".bin"); + int newVersion = checkForUpdates(fwURL + ".version"); + + if (newVersion < 1) { + ESP_LOGW(TAG, "[update] no device specific update found..\n"); + fwURL = getUpdateURL(deviceName, ".bin"); + newVersion = checkForUpdates(fwURL + ".version"); + // try project specific image + if (newVersion < 1) { + ESP_LOGW(TAG, "[update] Error while looking for updates..\n"); + return; + } + } + + if( newVersion > FW_VERSION ) { + ESP_LOGW(TAG, "Preparing to update.\n" ); + + ESP_LOGW(TAG, "Firmware image URL: " ); + ESP_LOGW(TAG, "%s\n", fwURL.c_str() ); + + WiFiClientSecure client; + client.setCACert(rootCACertificate); + + client.setTimeout(12); // seconds + + httpUpdate.setLedPin(LED_BUILTIN, HIGH); + + t_httpUpdate_return ret = httpUpdate.update( client, fwURL ); + + switch(ret) { + case HTTP_UPDATE_FAILED: + ESP_LOGW(TAG, "HTTP_UPDATE_FAILED Error (%d): %s\n", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str()); + break; + case HTTP_UPDATE_NO_UPDATES: + ESP_LOGW(TAG, "HTTP_UPDATE_NO_UPDATES\n"); + break; + case HTTP_UPDATE_OK: + ESP_LOGW(TAG, "[update] Update ok.\n"); // may not called we reboot the ESP + break; + } + } else { + ESP_LOGW(TAG, "[update] Already on latest version\n" ); + } +} + +int XD0OTA::checkForUpdates(String url) { + int newVersion = -1; + + ESP_LOGW(TAG, "Checking for firmware updates.\n" ); + ESP_LOGW(TAG, "Firmware version URL: " ); + ESP_LOGW(TAG, "%s\n", url.c_str() ); + + WiFiClientSecure client; + client.setCACert(rootCACertificate); + client.setTimeout(5); // seconds + + HTTPClient httpClient; + + httpClient.begin( client, url ); + int httpCode = httpClient.GET(); + if( httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY ) { + String newFWVersion = httpClient.getString(); + + ESP_LOGW(TAG, "Current firmware version: " ); + ESP_LOGW(TAG, "%s\n", String(FW_VERSION).c_str() ); + ESP_LOGW(TAG, "Available firmware version: " ); + ESP_LOGW(TAG, "%s\n", newFWVersion.c_str() ); + + newVersion = newFWVersion.toInt(); + } else { + ESP_LOGW(TAG, "Firmware version check failed, got HTTP response code " ); + ESP_LOGW(TAG, "%d\n", httpCode ); + newVersion = -1; + } + httpClient.end(); + return newVersion; +} diff --git a/src/XD0OTA.h b/src/XD0OTA.h new file mode 100644 index 0000000..c533dc7 --- /dev/null +++ b/src/XD0OTA.h @@ -0,0 +1,84 @@ +#ifndef _XD0OTA_H +#define _XD0OTA_H + +#include + +class XD0OTA { + public: + XD0OTA(String deviceName); + void update(void); + int checkForUpdates(String url); + private: + String deviceName; + const char* fwUrlBase = "https://fwupdate.xd0.de:444/fota/"; + const char* httpsFingerprint = "37 42 61 B9 E6 EE 22 36 D1 59 67 7D 55 53 6E A4 C7 AA 60 26"; + const char* rootCACertificate = \ + "-----BEGIN CERTIFICATE-----\n" \ + "MIID8DCCAtigAwIBAgIJAPSONy8RRejRMA0GCSqGSIb3DQEBCwUAMIGLMQswCQYD\n" \ + "VQQGEwJERTEeMBwGA1UECAwVTm9ydGhyaGluZS1XZXN0cGhhbGlhMQ4wDAYDVQQH\n" \ + "DAVFc3NlbjEPMA0GA1UECgwGeGQwLmRlMRgwFgYDVQQDDA9md3VwZGF0ZS54ZDAu\n" \ + "ZGUxITAfBgkqhkiG9w0BCQEWEmhlbmRyaWsrZGV2QHhkMC5kZTAgFw0xODA2MTYx\n" \ + "MTAzMTVaGA8yMDY4MDYxNjExMDMxNVowgYsxCzAJBgNVBAYTAkRFMR4wHAYDVQQI\n" \ + "DBVOb3J0aHJoaW5lLVdlc3RwaGFsaWExDjAMBgNVBAcMBUVzc2VuMQ8wDQYDVQQK\n" \ + "DAZ4ZDAuZGUxGDAWBgNVBAMMD2Z3dXBkYXRlLnhkMC5kZTEhMB8GCSqGSIb3DQEJ\n" \ + "ARYSaGVuZHJpaytkZXZAeGQwLmRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" \ + "CgKCAQEAx37S1YgaG74IhTysaaZQMFI50TDeGkxxdpNoIBX0UBeVtKI/3u3MAqBz\n" \ + "kKTDHFu4IQDj0PvBdlqPFGdSinFgrIvr49uAr+alNKUtkuSTT7nXI0fzqAxv1taj\n" \ + "0mNhigVvYikX2BUU/rNLnQclyBdPNVsOf9cv0t5+UcOHRt6oEwk5nFtG7s7k4+wu\n" \ + "wRdGlLy2LwLihYFon4LHAs05JW3qs0IQI4etc8E2JWjF2YwBg3+ooyzUFFIGjPSl\n" \ + "Lpi7WvAAR19HITbt5FJXQkFZnFxnfbQv/5f7n8vWfFmzYsEgvldwMZv+Eg6wPb2h\n" \ + "rgH7T6RSb55JrZE/JUY5C6pKvTJ3AwIDAQABo1MwUTAdBgNVHQ4EFgQUCnZywNj+\n" \ + "djz6n0sIARPx8dp+7bQwHwYDVR0jBBgwFoAUCnZywNj+djz6n0sIARPx8dp+7bQw\n" \ + "DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAto7IGXpNYTiPUgnA\n" \ + "DE6osdgSV1yVYJj75v+Y8aUGgQ93Ipl/0+PQL99wbGgjDhfxGADLtljwoEAz/fep\n" \ + "RqCh8swjL34XV9XjMzfhEMDCybSO6mK7ZmCKwhz9yBaK/Qjdj0YUoLhJ9Huzb70m\n" \ + "lGzbOhY4JJKFVaA5AcZhYHqjCmzHCVJ/H0zeuPGyKutbvSx23a24LmebfY4q2D62\n" \ + "U85ox3Ojek6Mc8J4V+RjORygDGAO4gZClEhAza4koAg7lCO/kSSk5PrXdlz2dqtA\n" \ + "D5Npv9M5363apnO1VlVR+OuO1NEJusRK1aWk9RLZsTPxzwOWwdkifXxUEJ+f8mGn\n" \ + "o+6SCw==\n" \ + "-----END CERTIFICATE-----\n"; + String getMAC(void); + String getUpdateURL(String file, String extension); + void setClock(void); +}; + +#endif /* _XD0OTA_H */ + + +/* + +MAKE SURE TO RESET THE ESP8266 ONCE AFTER NORMAL FLASHING! OTHERWISE OTA WON'T WORK! + + +openssl req -x509 -nodes -days 18263 -newkey rsa:2048 -keyout /etc/ssl/private/xd0-fwupdate-selfsigned.key -out /etc/ssl/certs/xd0-fwupdate-selfsigned.crt + +openssl x509 -noout -fingerprint -sha1 -inform pem -in /etc/ssl/certs/xd0-fwupdate-selfsigned.crt + + +scp .pioenvs/nodemcuv2/firmware.bin user@webserver:/var/www/fwupdate/fota/macaddress.bin +and change macaddress.version + + +server { + listen 444 ssl; + listen [::]:444 ssl; + server_name fwupdate.xd0.de; + + # SSL configuration + ssl_certificate /etc/ssl/certs/xd0-fwupdate-selfsigned.crt; + ssl_certificate_key /etc/ssl/private/xd0-fwupdate-selfsigned.key; + ssl_buffer_size 4k; + + root /var/www/fwupdate; + + location / { + # First attempt to serve request as file, then + # as directory, then fall back to displaying a 404. + try_files $uri $uri/ =404; + } + + access_log /var/log/nginx/fwupdate_access.log; + error_log /var/log/nginx/fwupdate_error.log; +} + +*/