installer.cc 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. #include <time.h>
  2. #include <unistd.h>
  3. #include <cstdlib>
  4. #include <fstream>
  5. #include <map>
  6. #include <QDebug>
  7. #include <QDir>
  8. #include <QTimer>
  9. #include <QVBoxLayout>
  10. #include "selfdrive/ui/installer/installer.h"
  11. #include "selfdrive/ui/qt/util.h"
  12. #include "selfdrive/ui/qt/qt_window.h"
  13. std::string get_str(std::string const s) {
  14. std::string::size_type pos = s.find('?');
  15. assert(pos != std::string::npos);
  16. return s.substr(0, pos);
  17. }
  18. // Leave some extra space for the fork installer
  19. const std::string GIT_URL = get_str("https://github.com/commaai/openpilot.git" "? ");
  20. const std::string BRANCH_STR = get_str(BRANCH "? ");
  21. #define GIT_SSH_URL "git@github.com:commaai/openpilot.git"
  22. #define CONTINUE_PATH "/data/continue.sh"
  23. const QString CACHE_PATH = "/data/openpilot.cache";
  24. #define INSTALL_PATH "/data/openpilot"
  25. #define TMP_INSTALL_PATH "/data/tmppilot"
  26. extern const uint8_t str_continue[] asm("_binary_selfdrive_ui_installer_continue_" BRAND "_sh_start");
  27. extern const uint8_t str_continue_end[] asm("_binary_selfdrive_ui_installer_continue_" BRAND "_sh_end");
  28. bool time_valid() {
  29. time_t rawtime;
  30. time(&rawtime);
  31. struct tm * sys_time = gmtime(&rawtime);
  32. return (1900 + sys_time->tm_year) >= 2020;
  33. }
  34. void run(const char* cmd) {
  35. int err = std::system(cmd);
  36. assert(err == 0);
  37. }
  38. Installer::Installer(QWidget *parent) : QWidget(parent) {
  39. QVBoxLayout *layout = new QVBoxLayout(this);
  40. layout->setContentsMargins(150, 290, 150, 150);
  41. layout->setSpacing(0);
  42. QLabel *title = new QLabel(tr("Installing..."));
  43. title->setStyleSheet("font-size: 90px; font-weight: 600;");
  44. layout->addWidget(title, 0, Qt::AlignTop);
  45. layout->addSpacing(170);
  46. bar = new QProgressBar();
  47. bar->setRange(0, 100);
  48. bar->setTextVisible(false);
  49. bar->setFixedHeight(72);
  50. layout->addWidget(bar, 0, Qt::AlignTop);
  51. layout->addSpacing(30);
  52. val = new QLabel("0%");
  53. val->setStyleSheet("font-size: 70px; font-weight: 300;");
  54. layout->addWidget(val, 0, Qt::AlignTop);
  55. layout->addStretch();
  56. QObject::connect(&proc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &Installer::cloneFinished);
  57. QObject::connect(&proc, &QProcess::readyReadStandardError, this, &Installer::readProgress);
  58. QTimer::singleShot(100, this, &Installer::doInstall);
  59. setStyleSheet(R"(
  60. * {
  61. font-family: Inter;
  62. color: white;
  63. background-color: black;
  64. }
  65. QProgressBar {
  66. border: none;
  67. background-color: #292929;
  68. }
  69. QProgressBar::chunk {
  70. background-color: #364DEF;
  71. }
  72. )");
  73. }
  74. void Installer::updateProgress(int percent) {
  75. bar->setValue(percent);
  76. val->setText(QString("%1%").arg(percent));
  77. update();
  78. }
  79. void Installer::doInstall() {
  80. // wait for valid time
  81. while (!time_valid()) {
  82. usleep(500 * 1000);
  83. qDebug() << "Waiting for valid time";
  84. }
  85. // cleanup previous install attempts
  86. run("rm -rf " TMP_INSTALL_PATH " " INSTALL_PATH);
  87. // do the install
  88. if (QDir(CACHE_PATH).exists()) {
  89. cachedFetch(CACHE_PATH);
  90. } else {
  91. freshClone();
  92. }
  93. }
  94. void Installer::freshClone() {
  95. qDebug() << "Doing fresh clone";
  96. proc.start("git", {"clone", "--progress", GIT_URL.c_str(), "-b", BRANCH_STR.c_str(),
  97. "--depth=1", "--recurse-submodules", TMP_INSTALL_PATH});
  98. }
  99. void Installer::cachedFetch(const QString &cache) {
  100. qDebug() << "Fetching with cache: " << cache;
  101. run(QString("cp -rp %1 %2").arg(cache, TMP_INSTALL_PATH).toStdString().c_str());
  102. int err = chdir(TMP_INSTALL_PATH);
  103. assert(err == 0);
  104. run(("git remote set-branches --add origin " + BRANCH_STR).c_str());
  105. updateProgress(10);
  106. proc.setWorkingDirectory(TMP_INSTALL_PATH);
  107. proc.start("git", {"fetch", "--progress", "origin", BRANCH_STR.c_str()});
  108. }
  109. void Installer::readProgress() {
  110. const QVector<QPair<QString, int>> stages = {
  111. // prefix, weight in percentage
  112. {tr("Receiving objects: "), 91},
  113. {tr("Resolving deltas: "), 2},
  114. {tr("Updating files: "), 7},
  115. };
  116. auto line = QString(proc.readAllStandardError());
  117. int base = 0;
  118. for (const QPair kv : stages) {
  119. if (line.startsWith(kv.first)) {
  120. auto perc = line.split(kv.first)[1].split("%")[0];
  121. int p = base + int(perc.toFloat() / 100. * kv.second);
  122. updateProgress(p);
  123. break;
  124. }
  125. base += kv.second;
  126. }
  127. }
  128. void Installer::cloneFinished(int exitCode, QProcess::ExitStatus exitStatus) {
  129. qDebug() << "git finished with " << exitCode;
  130. assert(exitCode == 0);
  131. updateProgress(100);
  132. // ensure correct branch is checked out
  133. int err = chdir(TMP_INSTALL_PATH);
  134. assert(err == 0);
  135. run(("git checkout " + BRANCH_STR).c_str());
  136. run(("git reset --hard origin/" + BRANCH_STR).c_str());
  137. run("git submodule update --init");
  138. // move into place
  139. run("mv " TMP_INSTALL_PATH " " INSTALL_PATH);
  140. #ifdef INTERNAL
  141. run("mkdir -p /data/params/d/");
  142. std::map<std::string, std::string> params = {
  143. {"SshEnabled", "1"},
  144. {"RecordFrontLock", "1"},
  145. {"GithubSshKeys", SSH_KEYS},
  146. };
  147. for (const auto& [key, value] : params) {
  148. std::ofstream param;
  149. param.open("/data/params/d/" + key);
  150. param << value;
  151. param.close();
  152. }
  153. run("cd " INSTALL_PATH " && "
  154. "git remote set-url origin --push " GIT_SSH_URL " && "
  155. "git config --replace-all remote.origin.fetch \"+refs/heads/*:refs/remotes/origin/*\"");
  156. #endif
  157. // write continue.sh
  158. FILE *of = fopen("/data/continue.sh.new", "wb");
  159. assert(of != NULL);
  160. size_t num = str_continue_end - str_continue;
  161. size_t num_written = fwrite(str_continue, 1, num, of);
  162. assert(num == num_written);
  163. fclose(of);
  164. run("chmod +x /data/continue.sh.new");
  165. run("mv /data/continue.sh.new " CONTINUE_PATH);
  166. // wait for the installed software's UI to take over
  167. QTimer::singleShot(60 * 1000, &QCoreApplication::quit);
  168. }
  169. int main(int argc, char *argv[]) {
  170. initApp(argc, argv);
  171. QApplication a(argc, argv);
  172. Installer installer;
  173. setMainWindow(&installer);
  174. return a.exec();
  175. }