diff --git a/tools/nik/installer_standalone.cc b/tools/nik/installer_standalone.cc new file mode 100644 index 0000000000..035113359a --- /dev/null +++ b/tools/nik/installer_standalone.cc @@ -0,0 +1,342 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// --- Configuration --- +#define GIT_URL "https://github.com/27182818284590452353602874713526624977572470936999595" +#define BRANCH "161803398874989484820458683436563811772030917980576286213544862270526046281890244970720720418939113748475408807538689175212663386222353693179318006076672635443338908659593958290563832266131992829026788067520876689250171169620703222104321626954862629631361" +#define LOADING_MSG "314159265358979323846264338327950288419" +#define GIT_SSH_URL "git@github.com:commaai/openpilot.git" +#define CONTINUE_PATH "/data/continue.sh" +#define CACHE_PATH "/usr/comma/openpilot" +#define INSTALL_PATH "/data/openpilot" +#define TMP_INSTALL_PATH "/data/tmppilot" + +// --- Embedded continue.sh --- +const char str_continue_data[] = R"(#!/usr/bin/env sh +exec /data/openpilot/launch_openpilot.sh +)"; +const uint8_t *str_continue = (const uint8_t*)str_continue_data; +const uint8_t *str_continue_end = str_continue + sizeof(str_continue_data) - 1; + +// --- Helper Functions --- +bool time_valid() { + time_t rawtime; + time(&rawtime); + struct tm * sys_time = gmtime(&rawtime); + return (1900 + sys_time->tm_year) >= 2020; +} + +void run(const char* cmd) { + int err = std::system(cmd); + assert(err == 0); +} + +float lerp(float a, float b, float f) { + return (a * (1.0 - f)) + (b * f); +} + +// --- Installer Class Definition --- +class Installer : public QWidget { +public: + Installer(QWidget *parent = nullptr); + + void updateProgress(int percent); + void doInstall(); + void freshClone(); + void cachedFetch(); + void readProgress(); + void cloneFinished(int exitCode, QProcess::ExitStatus exitStatus); + +private: + QProgressBar *bar; + QLabel *val; + QLabel *title; + QProcess proc; +}; + +// --- Globals --- +static QString targetGitUrl = GIT_URL; +static QString targetBranch = BRANCH; + +// --- Installer Implementation --- +Installer::Installer(QWidget *parent) : QWidget(parent) { + QStackedLayout *stack = new QStackedLayout(this); + stack->setMargin(0); + + // --- Page 0: Intro --- + QWidget *introPage = new QWidget; + QVBoxLayout *introLayout = new QVBoxLayout(introPage); + introLayout->setContentsMargins(50, 50, 50, 50); + introLayout->setSpacing(20); + + QLabel *introTitle = new QLabel("Select Installation Mode"); + introTitle->setStyleSheet("font-size: 60px; font-weight: 600; padding-bottom: 50px;"); + introTitle->setAlignment(Qt::AlignCenter); + introLayout->addWidget(introTitle); + + QPushButton *btnDefault = new QPushButton("Default Installation"); + btnDefault->setFixedHeight(120); + btnDefault->setStyleSheet("font-size: 40px; background-color: #333; border-radius: 10px;"); + introLayout->addWidget(btnDefault); + + QPushButton *btnBranch = new QPushButton("StarPilot Branch"); + btnBranch->setFixedHeight(120); + btnBranch->setStyleSheet("font-size: 40px; background-color: #333; border-radius: 10px;"); + introLayout->addWidget(btnBranch); + + stack->addWidget(introPage); + + // --- Page 1: Branch Selection --- + QWidget *branchPage = new QWidget; + QVBoxLayout *branchLayout = new QVBoxLayout(branchPage); + branchLayout->setContentsMargins(50, 50, 50, 50); + + QLabel *branchTitle = new QLabel("Select Branch"); + branchTitle->setStyleSheet("font-size: 50px; font-weight: 600;"); + branchLayout->addWidget(branchTitle); + + QListWidget *branchList = new QListWidget; + branchList->setStyleSheet("font-size: 35px; background-color: #222;"); + branchLayout->addWidget(branchList); + + QHBoxLayout *btnLayout = new QHBoxLayout; + QPushButton *btnCancel = new QPushButton("Cancel"); + btnCancel->setFixedHeight(100); + btnCancel->setStyleSheet("font-size: 35px; background-color: #444; border-radius: 10px;"); + + QPushButton *btnInstall = new QPushButton("Install"); + btnInstall->setFixedHeight(100); + btnInstall->setStyleSheet("font-size: 35px; background-color: #007aff; border-radius: 10px;"); + + btnLayout->addWidget(btnCancel); + btnLayout->addWidget(btnInstall); + branchLayout->addLayout(btnLayout); + + stack->addWidget(branchPage); + + // --- Page 2: Progress --- + QWidget *progressPage = new QWidget; + QVBoxLayout *layout = new QVBoxLayout(progressPage); + layout->setContentsMargins(150, 290, 150, 150); + layout->setSpacing(0); + + title = new QLabel("Installing " LOADING_MSG); + title->setStyleSheet("font-size: 90px; font-weight: 600;"); + layout->addWidget(title, 0, Qt::AlignTop); + + layout->addSpacing(170); + + bar = new QProgressBar(); + bar->setRange(0, 100); + bar->setTextVisible(false); + bar->setFixedHeight(72); + layout->addWidget(bar, 0, Qt::AlignTop); + + layout->addSpacing(30); + + val = new QLabel("0%"); + val->setStyleSheet("font-size: 70px; font-weight: 300;"); + layout->addWidget(val, 0, Qt::AlignTop); + + layout->addStretch(); + stack->addWidget(progressPage); + + // --- Logic Wiring --- + + // Default Button + QObject::connect(btnDefault, &QPushButton::clicked, [=]() { + stack->setCurrentWidget(progressPage); + QTimer::singleShot(100, this, &Installer::doInstall); + }); + + // StarPilot Button + QObject::connect(btnBranch, &QPushButton::clicked, [=]() { + stack->setCurrentWidget(branchPage); + branchList->clear(); + branchList->addItem("Loading branches..."); + + QProcess *p = new QProcess(this); + QString url = "https://github.com/firestar5683/StarPilot"; + p->start("git", {"ls-remote", "--heads", url}); + + QObject::connect(p, QOverload::of(&QProcess::finished), [=](int exitCode) { + branchList->clear(); + if (exitCode == 0) { + QString out = p->readAllStandardOutput(); + QStringList lines = out.split('\n'); + for (const QString &line : lines) { + if (line.isEmpty()) continue; + QStringList parts = line.split('\t'); + if (parts.size() < 2) continue; + QString ref = parts[1]; + if (ref.startsWith("refs/heads/")) { + branchList->addItem(ref.mid(11)); + } + } + } else { + branchList->addItem("Error fetching branches"); + } + p->deleteLater(); + }); + }); + + // Cancel Button + QObject::connect(btnCancel, &QPushButton::clicked, [=]() { + stack->setCurrentWidget(introPage); + }); + + // Install Button + QObject::connect(btnInstall, &QPushButton::clicked, [=]() { + if (branchList->currentItem()) { + targetGitUrl = "https://github.com/firestar5683/StarPilot"; + targetBranch = branchList->currentItem()->text(); + stack->setCurrentWidget(progressPage); + title->setText("Installing " + targetBranch); + QTimer::singleShot(100, this, &Installer::doInstall); + } + }); + + QObject::connect(&proc, QOverload::of(&QProcess::finished), this, &Installer::cloneFinished); + QObject::connect(&proc, &QProcess::readyReadStandardError, this, &Installer::readProgress); + + setStyleSheet(R"( + * { + font-family: Inter; + color: white; + background-color: black; + } + QProgressBar { + border: none; + background-color: #292929; + } + QListWidget { + border: 1px solid #444; + } + )"); +} + +void Installer::updateProgress(int percent) { + int h = (int)(lerp(233, 360 + 131, percent / 100.)) % 360; + int s = lerp(78, 62, percent / 100.); + int b = lerp(94, 87, percent / 100.); + + bar->setValue(percent); + bar->setStyleSheet(QString(R"( + QProgressBar::chunk { + background-color: hsb(%1, %2%, %3%); + })").arg(h).arg(s).arg(b)); + val->setText(QString("%1%").arg(percent)); + update(); +} + +void Installer::doInstall() { + while (!time_valid()) { + usleep(500 * 1000); + qDebug() << "Waiting for valid time"; + } + + run("rm -rf " TMP_INSTALL_PATH " " INSTALL_PATH); + + if (QDir(CACHE_PATH).exists()) { + cachedFetch(); + } else { + freshClone(); + } +} + +void Installer::freshClone() { + qDebug() << "Doing fresh clone"; + proc.start("git", {"clone", "--progress", targetGitUrl, "-b", targetBranch, + "--depth=1", "--recurse-submodules", TMP_INSTALL_PATH}); +} + +void Installer::cachedFetch() { + qDebug() << "Fetching with cache"; + + run("cp -rp " CACHE_PATH " " TMP_INSTALL_PATH); + int err = chdir(TMP_INSTALL_PATH); + assert(err == 0); + run(("git remote set-branches --add origin " + targetBranch.toStdString()).c_str()); + + updateProgress(10); + + proc.setWorkingDirectory(TMP_INSTALL_PATH); + proc.start("git", {"fetch", "--progress", "origin", targetBranch}); +} + +void Installer::readProgress() { + const QVector> stages = { + {"Receiving objects: ", 95}, + {"Filtering content: ", 5}, + }; + + auto line = QString(proc.readAllStandardError()); + + int base = 0; + for (const QPair kv : stages) { + if (line.startsWith(kv.first)) { + auto perc = line.split(kv.first)[1].split("%")[0]; + int p = base + int(perc.toFloat() / 100. * kv.second); + updateProgress(p); + break; + } + base += kv.second; + } +} + +void Installer::cloneFinished(int exitCode, QProcess::ExitStatus exitStatus) { + qDebug() << "git finished with " << exitCode; + assert(exitCode == 0); + + title->setText("Installation complete"); + updateProgress(100); + + int err = chdir(TMP_INSTALL_PATH); + assert(err == 0); + run(("git checkout " + targetBranch.toStdString()).c_str()); + run(("git reset --hard origin/" + targetBranch.toStdString()).c_str()); + + run("mv " TMP_INSTALL_PATH " " INSTALL_PATH); + +// Embedded continue.sh write logic + FILE *of = fopen("/data/continue.sh.new", "wb"); + assert(of != NULL); + + size_t num = str_continue_end - str_continue; + size_t num_written = fwrite(str_continue, 1, num, of); + assert(num == num_written); + fclose(of); + + run("chmod +x /data/continue.sh.new"); + run("mv /data/continue.sh.new " CONTINUE_PATH); + + QTimer::singleShot(60 * 1000, &QCoreApplication::quit); +} + +int main(int argc, char *argv[]) { + // Simplest main setup + QApplication a(argc, argv); + Installer installer; + installer.showFullScreen(); + return a.exec(); +}