diff --git a/build_only.sh b/build_only.sh new file mode 100755 index 0000000..19963aa --- /dev/null +++ b/build_only.sh @@ -0,0 +1,38 @@ +#!/usr/bin/bash +# CLEARPILOT: build-only mode — compile without starting manager. +# On failure: shows error on screen (non-blocking) and exits nonzero with stderr output. +# On success: exits 0, does not start manager. +# +# Usage: su - comma -c "bash /data/openpilot/build_only.sh" + +BASEDIR="/data/openpilot" + +# Fix ownership — we edit as root, openpilot builds/runs as comma +sudo chown -R comma:comma "$BASEDIR" + +# Kill stale error displays and any running manager/launch/managed processes +pkill -9 -f "selfdrive/ui/text" 2>/dev/null +pkill -9 -f 'launch_openpilot.sh' 2>/dev/null +pkill -9 -f 'launch_chffrplus.sh' 2>/dev/null +pkill -9 -f 'python.*manager.py' 2>/dev/null +pkill -9 -f 'selfdrive\.' 2>/dev/null +pkill -9 -f 'system\.' 2>/dev/null +pkill -9 -f './ui' 2>/dev/null +sleep 1 + +source "$BASEDIR/launch_env.sh" + +ln -sfn "$BASEDIR" /data/pythonpath +export PYTHONPATH="$BASEDIR" + +# Hardware init (GPU perms) +sudo chgrp gpu /dev/adsprpc-smd /dev/ion /dev/kgsl-3d0 2>/dev/null +sudo chmod 660 /dev/adsprpc-smd /dev/ion /dev/kgsl-3d0 2>/dev/null + +# Preflight: create dirs git can't track +source "$BASEDIR/build_preflight.sh" + +cd "$BASEDIR/selfdrive/manager" +rm -f "$BASEDIR/prebuilt" + +BUILD_ONLY=1 exec ./build.py diff --git a/build_preflight.sh b/build_preflight.sh new file mode 100644 index 0000000..14b64f5 --- /dev/null +++ b/build_preflight.sh @@ -0,0 +1,11 @@ +#!/usr/bin/bash +# CLEARPILOT: build preflight — create directories and fix state that +# git cannot track but the build requires. Called by build_only.sh and +# launch_chffrplus.sh before scons runs. + +BASEDIR="${BASEDIR:-/data/openpilot}" + +# SConscript files write generated headers into obj/ directories at +# parse time — these must exist before scons starts. +mkdir -p "$BASEDIR/body/board/obj" +mkdir -p "$BASEDIR/panda/board/obj" diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index 52689a8..1b3cb15 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -79,9 +79,15 @@ function launch { agnos_init fi + # CLEARPILOT: kill stale error display from previous build/run + pkill -f "selfdrive/ui/text" 2>/dev/null + # write tmux scrollback to a file tmux capture-pane -pq -S-1000 > /tmp/launch_log + # Preflight: create dirs git can't track + source "$DIR/build_preflight.sh" + # start manager cd selfdrive/manager if [ ! -f $DIR/prebuilt ]; then diff --git a/launch_openpilot.sh b/launch_openpilot.sh index 8c2a08f..4a0ac56 100755 --- a/launch_openpilot.sh +++ b/launch_openpilot.sh @@ -1,5 +1,38 @@ #!/usr/bin/bash +# Kill other instances of this script, launch chain, and all managed processes +for pid in $(pgrep -f 'launch_openpilot.sh' | grep -v $$); do + kill -9 "$pid" 2>/dev/null +done +for pid in $(pgrep -f 'launch_chffrplus.sh' | grep -v $$); do + kill -9 "$pid" 2>/dev/null +done +pkill -9 -f 'python.*manager.py' 2>/dev/null +# Kill all processes started by the manager (run as comma user, in openpilot tree) +pkill -9 -f 'selfdrive\.' 2>/dev/null +pkill -9 -f 'system\.' 2>/dev/null +pkill -9 -f './ui' 2>/dev/null +pkill -9 -f 'selfdrive/ui/text' 2>/dev/null +sleep 1 + +# CLEARPILOT: ensure params persistence dir is owned by comma:comma. Editing +# the tree as root leaves files owned by root in /data/params/d_tmp/, and +# Params writes done as comma will then EACCES on rename. Reset on every +# launch so this never silently breaks again. +sudo chown -R comma:comma /data/params + bash /data/openpilot/system/clearpilot/on_start.sh +# CLEARPILOT: start VPN monitor (kills previous instances, runs as root) +sudo bash -c 'nohup /data/openpilot/system/clearpilot/vpn-monitor.sh >> /tmp/vpn-monitor.log 2>&1 &' + +# CLEARPILOT: start nice monitor (keeps claude at nice 19) +sudo bash -c 'nohup /data/openpilot/system/clearpilot/nice-monitor.sh > /dev/null 2>&1 &' + +# CLEARPILOT: pass --bench flag through to manager via env var +if [ "$1" = "--bench" ]; then + export BENCH_MODE=1 +fi + +cd /data/openpilot exec ./launch_chffrplus.sh diff --git a/selfdrive/ui/qt/spinner b/selfdrive/ui/qt/spinner index 645bc44..ab69bdc 100755 Binary files a/selfdrive/ui/qt/spinner and b/selfdrive/ui/qt/spinner differ diff --git a/selfdrive/ui/qt/spinner.cc b/selfdrive/ui/qt/spinner.cc index 7ca2f1a..143e920 100755 --- a/selfdrive/ui/qt/spinner.cc +++ b/selfdrive/ui/qt/spinner.cc @@ -15,48 +15,24 @@ #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/util.h" -TrackWidget::TrackWidget(QWidget *parent) : QWidget(parent) { - setAttribute(Qt::WA_OpaquePaintEvent); - setFixedSize(spinner_size); - - // pre-compute all the track imgs. make this a gif instead? - QPixmap comma_img = loadPixmap("../assets/img_spinner_comma.png", spinner_size); - QPixmap track_img = loadPixmap("../assets/img_spinner_track.png", spinner_size); - - QTransform transform(1, 0, 0, 1, width() / 2, height() / 2); - QPixmap pm(spinner_size); - QPainter p(&pm); - p.setRenderHint(QPainter::SmoothPixmapTransform); - for (int i = 0; i < track_imgs.size(); ++i) { - p.resetTransform(); - p.fillRect(0, 0, spinner_size.width(), spinner_size.height(), Qt::black); - p.drawPixmap(0, 0, comma_img); - p.setTransform(transform.rotate(360 / spinner_fps)); - p.drawPixmap(-width() / 2, -height() / 2, track_img); - track_imgs[i] = pm.copy(); - } - - m_anim.setDuration(1000); - m_anim.setStartValue(0); - m_anim.setEndValue(int(track_imgs.size() -1)); - m_anim.setLoopCount(-1); - m_anim.start(); - connect(&m_anim, SIGNAL(valueChanged(QVariant)), SLOT(update())); -} - -void TrackWidget::paintEvent(QPaintEvent *event) { - QPainter painter(this); - painter.drawPixmap(0, 0, track_imgs[m_anim.currentValue().toInt()]); -} - -// Spinner +// CLEARPILOT: full-screen boot logo background with progress bar overlay Spinner::Spinner(QWidget *parent) : QWidget(parent) { + // Load boot logo as full-screen background, rotated 90° CCW + // (bg.jpg is pre-rotated 90° CW for the raw framebuffer) + QPixmap boot_logo("/usr/comma/bg.jpg"); + if (!boot_logo.isNull()) { + QTransform rot; + rot.rotate(-90); + bg_img = boot_logo.transformed(rot); + } + QGridLayout *main_layout = new QGridLayout(this); main_layout->setSpacing(0); - main_layout->setMargin(200); + main_layout->setMargin(0); - main_layout->addWidget(new TrackWidget(this), 0, 0, Qt::AlignHCenter | Qt::AlignVCenter); + // Spacer to push progress bar toward bottom + main_layout->setRowStretch(0, 1); text = new QLabel(); text->setWordWrap(true); @@ -69,7 +45,10 @@ Spinner::Spinner(QWidget *parent) : QWidget(parent) { progress_bar->setTextVisible(false); progress_bar->setVisible(false); progress_bar->setFixedHeight(20); - main_layout->addWidget(progress_bar, 1, 0, Qt::AlignHCenter); + main_layout->addWidget(progress_bar, 2, 0, Qt::AlignHCenter | Qt::AlignBottom); + + // Bottom margin for progress bar + main_layout->setContentsMargins(0, 0, 0, 80); setStyleSheet(R"( Spinner { @@ -88,7 +67,7 @@ Spinner::Spinner(QWidget *parent) : QWidget(parent) { } QProgressBar::chunk { border-radius: 10px; - background-color: rgba(23, 134, 68, 255); + background-color: white; } )"); @@ -96,6 +75,17 @@ Spinner::Spinner(QWidget *parent) : QWidget(parent) { QObject::connect(notifier, &QSocketNotifier::activated, this, &Spinner::update); } +void Spinner::paintEvent(QPaintEvent *event) { + QPainter p(this); + p.fillRect(rect(), Qt::black); + if (!bg_img.isNull()) { + QPixmap scaled = bg_img.scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + int x = (width() - scaled.width()) / 2; + int y = (height() - scaled.height()) / 2; + p.drawPixmap(x, y, scaled); + } +} + void Spinner::update(int n) { std::string line; std::getline(std::cin, line); diff --git a/selfdrive/ui/qt/spinner.h b/selfdrive/ui/qt/spinner.h index 43d90a7..b279a17 100755 --- a/selfdrive/ui/qt/spinner.h +++ b/selfdrive/ui/qt/spinner.h @@ -1,36 +1,23 @@ -#include - #include #include #include #include -#include #include -constexpr int spinner_fps = 30; -constexpr QSize spinner_size = QSize(360, 360); - -class TrackWidget : public QWidget { - Q_OBJECT -public: - TrackWidget(QWidget *parent = nullptr); - -private: - void paintEvent(QPaintEvent *event) override; - std::array track_imgs; - QVariantAnimation m_anim; -}; - class Spinner : public QWidget { Q_OBJECT public: explicit Spinner(QWidget *parent = 0); +protected: + void paintEvent(QPaintEvent *event) override; + private: QLabel *text; QProgressBar *progress_bar; QSocketNotifier *notifier; + QPixmap bg_img; public slots: void update(int n); diff --git a/system/clearpilot/dev/GithubSshKeys b/system/clearpilot/dev/GithubSshKeys index 4d49e5f..cbce3ff 100755 --- a/system/clearpilot/dev/GithubSshKeys +++ b/system/clearpilot/dev/GithubSshKeys @@ -1,3 +1,5 @@ ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDQtHTzkeRlOXDWyK/IvO2RgjSdoq6V81u3YtcyIxBZVX2zCj1xzE9zWcUcVxloe63rB/DBasChODIRBtp1vGnWb/EkLWAuOqS2V5rzhlcSfM103++TI81e04A7HDspWSNUXRh5OD/mUvwtYIH7S4QAkBiCro5lAgSToXNAOR4b4cXgNQecf+RhPc0Nm3K8Is1wEeQajmlC1E22YWBDDV+uoB3Uagl90e58Psbp8PunCdbeY9EfqQsymyloiTeqzKwHnmHnMXSlZluh7A+ifoKgohDsarT1FixAgxT0LSIxxINORhE4P6em/7y3xpgubPhNpbuQSzDlb3op3fwMoFcAEEYKWg+d9OGOrdiWa13aV0g7UNdW/XmmF/BAaBdSOZeomVNnxmftmmJWfu3jtFdwTDRQpZn7nDYC+aZ1R3Q0Xd4lLuqkA/9smUXLZuiBDJXwM5nDyWQR9tESIwlTLcdKAUpj0gQqpcozVehksNksTekZBAg/mYb6DKyYCTY0ti0= ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCm/Vq50kqf94allqGq9luBGjDh2Do/rOCA719CRlDOErCvdY+ZaYNumQZ5AbFfU5KcPZwirJLBvhEoH/G0lEAg9TUaUgH/VvqBBztlpcmA1eplZHzEFLnTDn0oO4Tk46bXwjL0anOZfNaUGhbaO4Th7m+9+o16WUduEabPiyVbnqD6P44CANsvBJNKlyUDBzsdkE9z5gULp06i1+JqqXiGV81HoFWZe5YCFv4j4QUPvfmFhcBHViVrOFs87hS4Eu0gWNxQmQBhh6R1ZbjaBlGdE5GyDZQZwlofjfuO06e0HvCDuIAELSYqlGFCmUhlM/LZ6YkF79/HFrg5sS3gsuY5 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHbrOZrByUb2Ci21DdJkhWv/4Bz4oghL9joraQYFq4Om +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOkbLtbZ6jRwmvYiAtXDp7JZ+IBVJIrxY3ZPJ93aQCha root@concordia +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCWTdewQ+jCUj9+ZJfte5h0meZhXayd7owIGyXHV0ELCeRAyQrurBPsdTTr7QhcugVuibI+Tr8L3BNuCN8nID5DH+BFAUejulGyMEmQ4Vh22p6Nt0niJHkfiJ2stfayPqe3qGRScVCcY3TpQqlzjyBWOvtwI9/118Gq/lKsjN7DYVwVMHhe1Yzh4SDHOKpsnmDfguRvCSzsg3ZeR1AduaqqM2y0sLZ09Cjpj/vJC/QQ2q2EWtzfmQPejFtjdbqvgoDEQ1OttD5dBgwCMOTPmPMmJ5ns6YJ4L+bi6hynO4/xn3efSHS2mSC6ACwiD/LtTMpsjbUVQsJ4pM/5GY08UoIdnH7P1N+6DA/hah+KAJe8U3WznT6OSXdwWXnYyV+hx4VHBz+/3MnbB1CwtoZoJDnQVpnT3IxBK+pnLHzZJh/g+bFrFbbAC50MRDsoV8nbYvHG3HJQ5GvK96S5NvllGTC/6zo/39ARvfrGtK/2NgNU+NZRjNN67cXjgcUIRdu6eJs= root@concordia diff --git a/system/clearpilot/dev/encrypt.sh b/system/clearpilot/dev/encrypt.sh index 970de21..c391125 100755 --- a/system/clearpilot/dev/encrypt.sh +++ b/system/clearpilot/dev/encrypt.sh @@ -1,17 +1,19 @@ #!/bin/bash -dongle_id=$(cat /data/params/d/DongleId) -if [[ ! $dongle_id == 90bb71* ]]; then +# Uses hardware serial as identity check and encryption key +serial=$(sed 's/.*androidboot.serialno=\([^ ]*\).*/\1/' /proc/cmdline) +if [[ $serial != 3889765b ]]; then + echo "Wrong device (serial=$serial)" exit 1 fi # Encrypt SSH keys if source files exist using the custom encrypt tool -if [ -f /data/openpilot/system/clearpilot/dev/id_rsa.pub ]; then - bash /data/openpilot/system/clearpilot/tools/encrypt /data/openpilot/system/clearpilot/dev/id_rsa.pub /data/openpilot/system/clearpilot/dev/id_rsa.pub.cpt +if [ -f /data/openpilot/system/clearpilot/dev/id_ed25519.pub ]; then + bash /data/openpilot/system/clearpilot/tools/encrypt /data/openpilot/system/clearpilot/dev/id_ed25519.pub /data/openpilot/system/clearpilot/dev/id_ed25519.pub.cpt fi -if [ -f /data/openpilot/system/clearpilot/dev/id_rsa ]; then - bash /data/openpilot/system/clearpilot/tools/encrypt /data/openpilot/system/clearpilot/dev/id_rsa /data/openpilot/system/clearpilot/dev/id_rsa.cpt +if [ -f /data/openpilot/system/clearpilot/dev/id_ed25519 ]; then + bash /data/openpilot/system/clearpilot/tools/encrypt /data/openpilot/system/clearpilot/dev/id_ed25519 /data/openpilot/system/clearpilot/dev/id_ed25519.cpt fi if [ -f /data/openpilot/system/clearpilot/dev/reverse_ssh ]; then diff --git a/system/clearpilot/dev/id_ed25519.cpt b/system/clearpilot/dev/id_ed25519.cpt new file mode 100644 index 0000000..ea4672b Binary files /dev/null and b/system/clearpilot/dev/id_ed25519.cpt differ diff --git a/system/clearpilot/dev/id_ed25519.pub.cpt b/system/clearpilot/dev/id_ed25519.pub.cpt new file mode 100644 index 0000000..4ca9c65 --- /dev/null +++ b/system/clearpilot/dev/id_ed25519.pub.cpt @@ -0,0 +1,2 @@ +--jq A3"|}.\`QA^~Lb m!z[W(rfo Mi[&oV=Q"2Ai 8p"!1G:4<- +# \ No newline at end of file diff --git a/system/clearpilot/dev/on_start.sh b/system/clearpilot/dev/on_start.sh deleted file mode 100755 index 040c433..0000000 --- a/system/clearpilot/dev/on_start.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -# tmp for debugging -date >> /tmp/dongles -echo check dongle >> /tmp/dongles -cat /data/params/d/DongleId >> /tmp/dongles -echo done >> /tmp/dongles - -dongle_id=$(cat /data/params/d/DongleId) -if [[ ! $dongle_id == 90bb71* ]]; then - exit 1 -fi - -echo Bringing up brian dev environment - -bash /data/openpilot/system/clearpilot/dev/provision.sh -bash /data/openpilot/system/clearpilot/dev/on_start_brian.sh \ No newline at end of file diff --git a/system/clearpilot/dev/on_start_brian.sh.cpt b/system/clearpilot/dev/on_start_brian.sh.cpt deleted file mode 100755 index 99f7de5..0000000 --- a/system/clearpilot/dev/on_start_brian.sh.cpt +++ /dev/null @@ -1,2 +0,0 @@ -͒T4odṳ喳!qs^1EQe|0b.7a|޶$)x 9S8BQ ;T`~?Q!hj2Ԍwq /[Xt5,˝m^v$vfH)JA -Wn`<@.&>&}m8;\$^`A \ No newline at end of file diff --git a/system/clearpilot/dev/provision.sh b/system/clearpilot/dev/provision.sh deleted file mode 100755 index 1c17a57..0000000 --- a/system/clearpilot/dev/provision.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash - -# Provision script for BrianBot -# These actions only occur on BrianBot's comma device. - -# 1. Check the string in /data/params/d/DongleId -dongle_id=$(cat /data/params/d/DongleId) -if [[ ! $dongle_id == 90bb71* ]]; then - exit 1 -fi - -echo "BrianBot dongle ID detected." - -# 2. Check if ccrypt is installed, install if not -if ! command -v ccrypt >/dev/null 2>&1; then - echo "Installing ccrypt..." - sudo apt-get update - sudo apt-get install -y ccrypt -fi - -# 3. Decrypt SSH keys if they have not been decrypted yet -if [ ! -f /data/openpilot/system/clearpilot/dev/id_rsa.pub ]; then - echo "Decrypting SSH keys..." - bash /data/openpilot/system/clearpilot/tools/decrypt /data/openpilot/system/clearpilot/dev/id_rsa.pub.cpt /data/openpilot/system/clearpilot/dev/id_rsa.pub - bash /data/openpilot/system/clearpilot/tools/decrypt /data/openpilot/system/clearpilot/dev/id_rsa.cpt /data/openpilot/system/clearpilot/dev/id_rsa - bash /data/openpilot/system/clearpilot/tools/decrypt /data/openpilot/system/clearpilot/dev/on_start_brian.sh.cpt /data/openpilot/system/clearpilot/dev/on_start_brian.sh -fi - -# 4. Ensure .ssh directory and keys exist -ssh_dir="/data/ssh/.ssh" -if [[ ! -f "$ssh_dir/id_rsa" || ! -f "$ssh_dir/id_rsa.pub" ]]; then - echo "Setting up SSH directory and keys..." - mkdir -p "$ssh_dir" - cp /data/openpilot/system/clearpilot/dev/id_rsa /data/openpilot/system/clearpilot/dev/id_rsa.pub "$ssh_dir" - chmod 700 "$ssh_dir" - chmod 600 "$ssh_dir/id_rsa" "$ssh_dir/id_rsa.pub" - echo hansonxyz > /data/params/d/GithubUsername - cat /data/openpilot/system/clearpilot/dev/GithubSshKeys > /data/params/d/GithubSshKeys - echo 1 > /data/params/d/SshEnabled - sudo systemctl restart ssh - cd /data/openpilot - git remote remove origin - git remote add origin git@privategit.hanson.xyz:brianhansonxyz/clearpilot.git -fi - -echo "Script execution complete." - - diff --git a/system/clearpilot/nice-monitor.sh b/system/clearpilot/nice-monitor.sh new file mode 100755 index 0000000..354690b --- /dev/null +++ b/system/clearpilot/nice-monitor.sh @@ -0,0 +1,19 @@ +#!/usr/bin/bash + +# Nice monitor — ensures claude processes run at lowest CPU priority. +# Checks every 30 seconds and renices any claude process not already at nice 19. + +# Kill other instances of this script +for pid in $(pgrep -f 'nice-monitor.sh' | grep -v $$); do + kill "$pid" 2>/dev/null +done + +while true; do + for pid in $(pgrep -f 'claude' 2>/dev/null); do + cur=$(awk '{print $19}' /proc/$pid/stat 2>/dev/null) + if [ -n "$cur" ] && [ "$cur" != "19" ]; then + renice 19 -p "$pid" > /dev/null 2>&1 + fi + done + sleep 30 +done diff --git a/system/clearpilot/on_start.sh b/system/clearpilot/on_start.sh index 380ee81..7fc82dd 100755 --- a/system/clearpilot/on_start.sh +++ b/system/clearpilot/on_start.sh @@ -3,5 +3,45 @@ # Install logo bash /data/openpilot/system/clearpilot/startup_logo/set_logo.sh -# Reverse ssh if brianbot dongle id -bash /data/openpilot/system/clearpilot/dev/on_start.sh +# SSH — always, unconditionally, first thing +cat /data/openpilot/system/clearpilot/dev/GithubSshKeys > /data/params/d/GithubSshKeys +echo -n 1 > /data/params/d/SshEnabled +sudo systemctl enable ssh 2>/dev/null +sudo systemctl start ssh + +# Decrypt and install SSH identity keys for root (git auth) +serial=$(sed 's/.*androidboot.serialno=\([^ ]*\).*/\1/' /proc/cmdline) +ssh_dir="/root/.ssh" +if [[ $serial == 3889765b ]] && [[ ! -f "$ssh_dir/id_ed25519" || ! -f "$ssh_dir/id_ed25519.pub" ]]; then + echo "Decrypting SSH identity keys for root (serial=$serial)..." + tmpdir=$(mktemp -d) + bash /data/openpilot/system/clearpilot/tools/decrypt /data/openpilot/system/clearpilot/dev/id_ed25519.cpt "$tmpdir/id_ed25519" + bash /data/openpilot/system/clearpilot/tools/decrypt /data/openpilot/system/clearpilot/dev/id_ed25519.pub.cpt "$tmpdir/id_ed25519.pub" + sudo mkdir -p "$ssh_dir" + sudo cp "$tmpdir/id_ed25519" "$tmpdir/id_ed25519.pub" "$ssh_dir/" + rm -rf "$tmpdir" + sudo chmod 700 "$ssh_dir" + sudo chmod 600 "$ssh_dir/id_ed25519" + sudo chmod 644 "$ssh_dir/id_ed25519.pub" + echo "SSH identity keys installed to $ssh_dir" +fi + +# Ensure root SSH config has git.hanson.xyz entry +if ! grep -q "Host git.hanson.xyz" "$ssh_dir/config" 2>/dev/null; then + sudo tee -a "$ssh_dir/config" > /dev/null <<'SSHCFG' + +Host git.hanson.xyz + IdentityFile /root/.ssh/id_ed25519 + StrictHostKeyChecking no +SSHCFG + sudo chmod 600 "$ssh_dir/config" + echo "SSH config updated for git.hanson.xyz" +fi + +# Always ensure WiFi radio is on +nmcli radio wifi on 2>/dev/null + +# Provision (packages, git pull, build) if no quick_boot flag +if [ ! -f /data/quick_boot ]; then + sudo bash /data/openpilot/system/clearpilot/provision.sh +fi diff --git a/system/clearpilot/provision.sh b/system/clearpilot/provision.sh new file mode 100644 index 0000000..a814918 --- /dev/null +++ b/system/clearpilot/provision.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +# ClearPilot provision script +# Runs on first boot (no /data/quick_boot) when internet is available. +# Installs packages, pulls latest code, and builds. +# SSH is handled by on_start.sh before this runs. +# Output is displayed on screen via qt_shell. + +mount -o rw,remount / + +# 1. Wait for internet connectivity +echo "Waiting for internet connectivity (up to 30s)..." +ONLINE=0 +for i in $(seq 1 30); do + if nmcli networking connectivity check 2>/dev/null | grep -q "full"; then + echo "Online after ${i}s" + ONLINE=1 + break + fi + sleep 1 +done + +if [ "$ONLINE" -eq 0 ]; then + echo "No internet after 30s, skipping packages and updates" + sleep 3 + exit 0 +fi + +# 3. Install packages +echo "Remounting / read-write..." +sudo mount -o remount,rw / +echo "Installing packages..." +sudo apt-get update -qq +sudo apt-get install -y openvpn curl ccrypt +#echo "Installing Node.js 20..." +#curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - +sudo apt-get install -y nodejs +mount -o rw,remount / +echo "Installing Claude Code..." +curl -fsSL https://claude.ai/install.sh | bash +cat > /usr/local/bin/claude <<'WRAPPER' +#!/bin/bash +sudo mount -o rw,remount / +exec /root/.local/bin/claude "$@" +WRAPPER +chmod +x /usr/local/bin/claude +echo "Packages installed" +# 4. Ensure git remote uses SSH (not HTTPS) +cd /data/openpilot +EXPECTED_REMOTE="git@git.hanson.xyz:brianhansonxyz/clearpilot.git" +CURRENT_REMOTE=$(git remote get-url origin 2>/dev/null) +if [ "$CURRENT_REMOTE" != "$EXPECTED_REMOTE" ]; then + echo "Fixing git remote: $CURRENT_REMOTE -> $EXPECTED_REMOTE" + git remote set-url origin "$EXPECTED_REMOTE" +fi + +# 5. Pull latest from remote (remote always wins) +echo "Checking for updates..." +git fetch origin clearpilot +LOCAL=$(git rev-parse HEAD) +REMOTE=$(git rev-parse origin/clearpilot) +if [ "$LOCAL" != "$REMOTE" ]; then + echo "Updating: $(git log --oneline -1 HEAD) -> $(git log --oneline -1 origin/clearpilot)" + git reset --hard origin/clearpilot + sudo chown -R comma:comma /data/openpilot + echo "Update complete" +else + echo "Already up to date" +fi + +# 5. Build +echo "" +sudo chown -R comma:comma /data/openpilot +touch /data/quick_boot + +echo "Provision complete" +sleep 2 diff --git a/system/clearpilot/provision_wrapper.sh b/system/clearpilot/provision_wrapper.sh new file mode 100644 index 0000000..2dfa0b2 --- /dev/null +++ b/system/clearpilot/provision_wrapper.sh @@ -0,0 +1,2 @@ +#!/bin/bash +exec bash /data/openpilot/system/clearpilot/provision.sh 2>&1 diff --git a/system/clearpilot/startup_logo/bg.jpg b/system/clearpilot/startup_logo/bg.jpg index 911630f..55c651f 100755 Binary files a/system/clearpilot/startup_logo/bg.jpg and b/system/clearpilot/startup_logo/bg.jpg differ diff --git a/system/clearpilot/startup_logo/generate_logo.sh b/system/clearpilot/startup_logo/generate_logo.sh index b6d0b17..6a6b742 100755 --- a/system/clearpilot/startup_logo/generate_logo.sh +++ b/system/clearpilot/startup_logo/generate_logo.sh @@ -3,8 +3,9 @@ # Create a 2160x1080 true color bitmap canvas with black background convert -size 2160x1080 canvas:black /tmp/black_canvas.png -# Place the image in the center of the canvas, blending the transparent background -composite -gravity center /data/openpilot/selfdrive/clearpilot/theme/clearpilot/images/boot_logo.png /tmp/black_canvas.png /tmp/centered_image.png +# Scale logo 140% then center on canvas +convert /data/openpilot/selfdrive/clearpilot/theme/clearpilot/images/boot_logo.png -resize 140% /tmp/scaled_logo.png +composite -gravity center /tmp/scaled_logo.png /tmp/black_canvas.png /tmp/centered_image.png # Rotate the image clockwise 90 degrees convert /tmp/centered_image.png -rotate 90 /tmp/rotated_image.png @@ -13,4 +14,4 @@ convert /tmp/centered_image.png -rotate 90 /tmp/rotated_image.png convert /tmp/rotated_image.png -quality 95 /data/openpilot/system/clearpilot/startup_logo/bg.jpg # Clean up temporary files -rm /tmp/black_canvas.png /tmp/centered_image.png /tmp/rotated_image.png +rm /tmp/black_canvas.png /tmp/scaled_logo.png /tmp/centered_image.png /tmp/rotated_image.png diff --git a/system/clearpilot/startup_logo/set_logo.sh b/system/clearpilot/startup_logo/set_logo.sh index 4b1533a..0b19f51 100755 --- a/system/clearpilot/startup_logo/set_logo.sh +++ b/system/clearpilot/startup_logo/set_logo.sh @@ -3,6 +3,13 @@ set -x +# CLEARPILOT: regenerate bg.jpg if boot_logo.png is newer (handles logo changes) +BOOT_LOGO="/data/openpilot/selfdrive/clearpilot/theme/clearpilot/images/boot_logo.png" +GENERATED_BG="/data/openpilot/system/clearpilot/startup_logo/bg.jpg" +if [ "$BOOT_LOGO" -nt "$GENERATED_BG" ] 2>/dev/null; then + bash /data/openpilot/system/clearpilot/startup_logo/generate_logo.sh +fi + # Check if md5sum of /usr/comma/bg.jpg is not equal to md5sum of /data/openpilot/system/clearpilot/startup_logo/bg.jpg if [ "$(md5sum /usr/comma/bg.jpg | awk '{print $1}')" != "$(md5sum /data/openpilot/system/clearpilot/startup_logo/bg.jpg | awk '{print $1}')" ]; then bash /data/openpilot/system/clearpilot/startup_logo/generate_logo.sh diff --git a/system/clearpilot/tools/decrypt b/system/clearpilot/tools/decrypt index a736b82..836cb95 100755 --- a/system/clearpilot/tools/decrypt +++ b/system/clearpilot/tools/decrypt @@ -10,8 +10,11 @@ fi src="$1" dest="$2" -# Read DongleId for decryption key -dongle_id=/data/params/d/DongleId +# Use hardware serial as decryption key +serial=$(sed 's/.*androidboot.serialno=\([^ ]*\).*/\1/' /proc/cmdline) +keyfile=$(mktemp) +echo -n "$serial" > "$keyfile" # Decrypt the file -cat "$src" | ccrypt -d -k "$dongle_id" > "$dest" +cat "$src" | ccrypt -d -k "$keyfile" > "$dest" +rm -f "$keyfile" diff --git a/system/clearpilot/tools/encrypt b/system/clearpilot/tools/encrypt index 9496892..49b112e 100755 --- a/system/clearpilot/tools/encrypt +++ b/system/clearpilot/tools/encrypt @@ -10,8 +10,11 @@ fi src="$1" dest="$2" -# Read DongleId for encryption key -dongle_id=/data/params/d/DongleId +# Use hardware serial as encryption key +serial=$(sed 's/.*androidboot.serialno=\([^ ]*\).*/\1/' /proc/cmdline) +keyfile=$(mktemp) +echo -n "$serial" > "$keyfile" # Encrypt the file -cat "$src" | ccrypt -e -k "$dongle_id" > "$dest" +cat "$src" | ccrypt -e -k "$keyfile" > "$dest" +rm -f "$keyfile" diff --git a/system/clearpilot/vpn-monitor.sh b/system/clearpilot/vpn-monitor.sh new file mode 100755 index 0000000..0f9a6a5 --- /dev/null +++ b/system/clearpilot/vpn-monitor.sh @@ -0,0 +1,153 @@ +#!/usr/bin/bash + +# VPN monitor — connects OpenVPN when internet is up, disconnects when down. +# Drops and reconnects when WiFi SSID changes (stale tunnel prevention). +# On non-home networks, resolves VPN hostname via 8.8.8.8 and passes IP directly. +# Keepalive: pings gateway through tunnel, two failures 10s apart = reconnect. +# SIGTERM: gracefully stops tunnel and exits. + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +CONF="$SCRIPT_DIR/vpn.ovpn" +VPN_HOST="vpn.hanson.xyz" +VPN_PORT="1194" +HOME_SSID="risa" +VPN_GW="192.168.69.1" +CHECK_HOST="1.1.1.1" +INTERVAL=30 +CONNECT_TIMEOUT=30 +MAX_FAILURES=3 +PREV_SSID="" +FAIL_COUNT=0 +ACTIVE_VPN_IP="" + +kill_vpn() { + killall openvpn 2>/dev/null + # Clean up host route to VPN server + if [ -n "$ACTIVE_VPN_IP" ]; then + ip route del "$ACTIVE_VPN_IP/32" 2>/dev/null + ACTIVE_VPN_IP="" + fi +} + +get_default_gw() { + ip route show default | awk '/via/ {print $3; exit}' +} + +resolve_vpn() { + if [ "$CURR_SSID" != "$HOME_SSID" ]; then + dig +short @8.8.8.8 "$VPN_HOST" 2>/dev/null | tail -1 + else + dig +short "$VPN_HOST" 2>/dev/null | tail -1 + fi +} + +# Graceful shutdown on SIGTERM +shutdown() { + echo "$(date): SIGTERM received, stopping vpn and exiting" + kill_vpn + exit 0 +} +trap shutdown SIGTERM SIGINT + +# Kill other instances of this script and wait for graceful shutdown +for pid in $(pgrep -f 'vpn-monitor.sh' | grep -v $$); do + kill "$pid" 2>/dev/null +done +sleep 5 +# Force kill any that didn't exit +for pid in $(pgrep -f 'vpn-monitor.sh' | grep -v $$); do + kill -9 "$pid" 2>/dev/null +done + +# Kill any existing VPN and clean up +kill_vpn +sleep 1 + +while true; do + CURR_SSID="$(iwgetid -r 2>/dev/null)" + + # Detect SSID change (only when switching between two known networks) + if [ -n "$PREV_SSID" ] && [ -n "$CURR_SSID" ] && [ "$PREV_SSID" != "$CURR_SSID" ]; then + echo "$(date): wifi changed from '$PREV_SSID' to '$CURR_SSID', dropping vpn" + kill_vpn + FAIL_COUNT=0 + sleep 5 + fi + PREV_SSID="$CURR_SSID" + + if ping -c 1 -W 3 "$CHECK_HOST" > /dev/null 2>&1; then + # Internet is up — check tunnel health if connected + if ip link show tun0 > /dev/null 2>&1; then + # Keepalive: ping gateway through tunnel, two failures 10s apart = dead + if ! ping -c 1 -W 3 -I tun0 "$VPN_GW" > /dev/null 2>&1; then + sleep 10 + if ! ping -c 1 -W 3 -I tun0 "$VPN_GW" > /dev/null 2>&1; then + echo "$(date): keepalive failed twice, dropping vpn" + kill_vpn + sleep 5 + continue + fi + fi + fi + + # Start VPN if not running + if ! ip link show tun0 > /dev/null 2>&1; then + if [ "$FAIL_COUNT" -ge "$MAX_FAILURES" ]; then + # Back off after repeated failures — just wait for next interval + sleep "$INTERVAL" + continue + fi + + # Resolve VPN server IP (via 8.8.8.8 on non-home networks) + RESOLVED_IP="$(resolve_vpn)" + if [ -z "$RESOLVED_IP" ]; then + echo "$(date): failed to resolve $VPN_HOST (ssid=$CURR_SSID)" + FAIL_COUNT=$((FAIL_COUNT + 1)) + sleep "$INTERVAL" + continue + fi + + # Add host route to VPN server via current default gateway + # so VPN traffic survives tun0 coming up + GW="$(get_default_gw)" + if [ -n "$GW" ]; then + ip route replace "$RESOLVED_IP/32" via "$GW" + echo "$(date): host route $RESOLVED_IP via $GW" + fi + ACTIVE_VPN_IP="$RESOLVED_IP" + + echo "$(date): starting openvpn -> $RESOLVED_IP (attempt $((FAIL_COUNT + 1))/$MAX_FAILURES, ssid=$CURR_SSID)" + nice -n 19 openvpn --config "$CONF" --remote "$RESOLVED_IP" "$VPN_PORT" --daemon --log-append /tmp/openvpn.log + + # Wait for tunnel to come up + CONNECTED=0 + for i in $(seq 1 "$CONNECT_TIMEOUT"); do + if ip link show tun0 > /dev/null 2>&1; then + CONNECTED=1 + break + fi + sleep 1 + done + + if [ "$CONNECTED" -eq 1 ]; then + echo "$(date): vpn connected (took ${i}s)" + FAIL_COUNT=0 + else + echo "$(date): vpn failed to connect within ${CONNECT_TIMEOUT}s, killing" + kill_vpn + FAIL_COUNT=$((FAIL_COUNT + 1)) + if [ "$FAIL_COUNT" -ge "$MAX_FAILURES" ]; then + echo "$(date): $MAX_FAILURES consecutive failures, backing off" + fi + fi + fi + else + # Internet is down — kill VPN if running + if ip link show tun0 > /dev/null 2>&1; then + echo "$(date): internet down, stopping openvpn" + kill_vpn + fi + FAIL_COUNT=0 + fi + sleep "$INTERVAL" +done diff --git a/system/clearpilot/vpn.ovpn b/system/clearpilot/vpn.ovpn new file mode 100644 index 0000000..8fd73ec --- /dev/null +++ b/system/clearpilot/vpn.ovpn @@ -0,0 +1,76 @@ +client +dev tun +proto udp +remote vpn.hanson.xyz 1194 +resolv-retry infinite +nobind +persist-key +persist-tun +remote-cert-tls server +cipher AES-256-GCM +auth SHA256 +verb 3 +pull-filter ignore "redirect-gateway" +# pull-filter ignore "route " + + +-----BEGIN CERTIFICATE----- +MIIB2jCCAX+gAwIBAgIUFVGjbK1Qb5d3RkkoNPMsXeI/xVAwCgYIKoZIzj0EAwIw +HjEcMBoGA1UEAwwTT3BlblZQTi1JbnRlcm5hbC1DQTAgFw0yNjAyMDcwODQ3Mzda +GA8yMTI2MDExNDA4NDczN1owHjEcMBoGA1UEAwwTT3BlblZQTi1JbnRlcm5hbC1D +QTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGb6RWOFnCJ9t7X5q6fqpv0y3Hg/ +dTU3ky+MAjfPRYfUWfiM7wVKubYOCc+pUHsJXWaghqu7nQoCeSzVDcPXlWGjgZgw +gZUwDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQUNThAWabF1zsqNE19iCKuZMjHBIUw +WQYDVR0jBFIwUIAUNThAWabF1zsqNE19iCKuZMjHBIWhIqQgMB4xHDAaBgNVBAMM +E09wZW5WUE4tSW50ZXJuYWwtQ0GCFBVRo2ytUG+Xd0ZJKDTzLF3iP8VQMAsGA1Ud +DwQEAwIBBjAKBggqhkjOPQQDAgNJADBGAiEA2mPwEK8G4HXlRu6WZVSRdqyCPYYd +KffYalCXgw3pZ/sCIQC9qPNckHtubycu8kq4iM8Vl1vYMVEorn7DUFdXJCvtcg== +-----END CERTIFICATE----- + + + +-----BEGIN CERTIFICATE----- +MIIB2TCCAYCgAwIBAgIRALuRBSB68/ccWM8SASfEIV0wCgYIKoZIzj0EAwIwHjEc +MBoGA1UEAwwTT3BlblZQTi1JbnRlcm5hbC1DQTAgFw0yNjA0MTIwMDA1NDhaGA8y +MTI2MDMxOTAwMDU0OFowEDEOMAwGA1UEAwwFY29tbWEwWTATBgcqhkjOPQIBBggq +hkjOPQMBBwNCAAQ/jN83Z2Ikk+IWVPGxN0CNFCh74Yrb3W6VXAjGWa+ppVxSbdeq +YVBWjJl6qSg6n2ZMDivQ5NcKgsxMcY9ly/LEo4GqMIGnMAkGA1UdEwQCMAAwHQYD +VR0OBBYEFDIulLc8hAwTkGHq+z8K8eBBM0vVMFkGA1UdIwRSMFCAFDU4QFmmxdc7 +KjRNfYgirmTIxwSFoSKkIDAeMRwwGgYDVQQDDBNPcGVuVlBOLUludGVybmFsLUNB +ghQVUaNsrVBvl3dGSSg08yxd4j/FUDATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNV +HQ8EBAMCB4AwCgYIKoZIzj0EAwIDRwAwRAIgR/ssLDNLmt1s0WXwGLszBUrlstUu +9nhP2PcmdnsOit4CIECFbQ7RHEZLQJWsL2DvKowCCzDtA6ZGDILTVfHwNyDn +-----END CERTIFICATE----- + + + +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgYolghDmo5ISWxjQy +sayXuFRSW5fkiIXJ1SGvSRLnmBmhRANCAAQ/jN83Z2Ikk+IWVPGxN0CNFCh74Yrb +3W6VXAjGWa+ppVxSbdeqYVBWjJl6qSg6n2ZMDivQ5NcKgsxMcY9ly/LE +-----END PRIVATE KEY----- + + + +# +# 2048 bit OpenVPN static key +# +-----BEGIN OpenVPN Static key V1----- +5d6fedacbb44013958eef494b179f21d +51b158484c08cb125b8ddd2a919ed44f +5cae951b1f85f483f0108b1000fac1e6 +334ab5b2f3c7352c3a53e814e2e4cdc7 +f401d5eb2e13449539313f18de53563d +a72318979c31ef76caad86317064aede +940ab3d799886b9667f4deabb8b159c2 +12bd7f27c91a7bfd3b9a315dbac3391d +fb3c354b7955627937fd6163c1683705 +e46b252ee9c383507b5a4496462f3d67 +25dc48bbca8170574efa22b3c37c4bcc +ad30e92d39aae5326c59a4484302d388 +7836837bd5098faeda430aa6db69d8df +fe62aeed2bef6afb7c0c742fe8644040 +3c4e46deb3915467c351018592c58545 +5b5d7b8c204d37104f9848573d8eb73b +-----END OpenVPN Static key V1----- +