mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-12 21:02:13 +08:00
GLXY Client Changes
This commit is contained in:
committed by
firestar5683
parent
b631895e66
commit
d7d844ae35
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import hmac
|
||||
import json
|
||||
import platform
|
||||
import shutil
|
||||
import signal
|
||||
@@ -20,25 +22,47 @@ AUTH_PORT = 8083
|
||||
process = None
|
||||
auth_server = None
|
||||
|
||||
VALID_PATHS = {"/glxylogin": "glxyauth", "/glxyverify": "glxysession"}
|
||||
|
||||
|
||||
class AuthHandler(BaseHTTPRequestHandler):
|
||||
"""Serves only GET /glxyauth — returns the PIN hash file contents."""
|
||||
def do_GET(self):
|
||||
if self.path == "/glxyauth":
|
||||
auth_file = GALAXY_DIR / "glxyauth"
|
||||
if auth_file.exists():
|
||||
data = auth_file.read_bytes()
|
||||
"""Validates login hashes and verifies session tokens."""
|
||||
dongle_id = ""
|
||||
|
||||
def do_POST(self):
|
||||
target = VALID_PATHS.get(self.path)
|
||||
if not target:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
return
|
||||
|
||||
file_path = GALAXY_DIR / target
|
||||
if not file_path.exists():
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
return
|
||||
|
||||
try:
|
||||
length = int(self.headers.get("Content-Length", 0))
|
||||
body = self.rfile.read(length).decode("utf-8").strip()
|
||||
if hmac.compare_digest(body, file_path.read_text().strip()):
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/plain")
|
||||
self.send_header("Content-Length", str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
if self.path == "/glxylogin":
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.end_headers()
|
||||
token = (GALAXY_DIR / "glxysession").read_text().strip()
|
||||
self.wfile.write(json.dumps({"dongle_id": self.dongle_id, "token": token}).encode())
|
||||
else:
|
||||
self.end_headers()
|
||||
return
|
||||
self.send_response(404)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.send_response(403)
|
||||
self.end_headers()
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass # suppress request logs
|
||||
pass
|
||||
|
||||
|
||||
def start_auth_server():
|
||||
@@ -111,7 +135,6 @@ def main():
|
||||
signal.signal(signal.SIGTERM, cleanup_frpc)
|
||||
signal.signal(signal.SIGINT, cleanup_frpc)
|
||||
|
||||
# Wait for DongleId to be set (usually set on boot/pairing)
|
||||
dongle_id = params.get("DongleId", encoding='utf8')
|
||||
while not dongle_id:
|
||||
print("Galaxy: Waiting for DongleId...")
|
||||
@@ -119,16 +142,22 @@ def main():
|
||||
dongle_id = params.get("DongleId", encoding='utf8')
|
||||
|
||||
print(f"Galaxy: DongleId: {dongle_id}")
|
||||
AuthHandler.dongle_id = dongle_id
|
||||
print("Galaxy: Starting manager loop...")
|
||||
|
||||
last_slug = None
|
||||
|
||||
while True:
|
||||
glxyauth_file = GALAXY_DIR / "glxyauth"
|
||||
galaxy_password_hash = glxyauth_file.read_text().strip() if glxyauth_file.exists() else None
|
||||
is_paired = galaxy_password_hash and len(galaxy_password_hash) == 64
|
||||
slug_file = GALAXY_DIR / "glxyslug"
|
||||
slug = slug_file.read_text().strip() if slug_file.exists() else None
|
||||
is_paired = galaxy_password_hash and len(galaxy_password_hash) == 64 and slug
|
||||
|
||||
if is_paired:
|
||||
if process is None or process.poll() is not None:
|
||||
if process is not None:
|
||||
if process is None or process.poll() is not None or slug != last_slug:
|
||||
cleanup_frpc()
|
||||
if process is not None and slug == last_slug:
|
||||
print(f"Galaxy: frpc exited with code {process.returncode}. Restarting...")
|
||||
|
||||
print("Galaxy: Password set. Preparing frpc tunnel...")
|
||||
@@ -137,7 +166,6 @@ def main():
|
||||
time.sleep(10)
|
||||
continue
|
||||
|
||||
# Start the tiny auth HTTP server (serves /glxyauth on localhost)
|
||||
start_auth_server()
|
||||
|
||||
frpc_toml = GALAXY_DIR / "frpc.toml"
|
||||
@@ -150,37 +178,38 @@ tls.enable = true
|
||||
poolCount = 2
|
||||
|
||||
[[proxies]]
|
||||
name = "{dongle_id}_pond"
|
||||
name = "{slug}_pond"
|
||||
type = "http"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 8082
|
||||
customDomains = ["{dongle_id}.devices.local"]
|
||||
customDomains = ["{slug}.devices.local"]
|
||||
transport.useCompression = true
|
||||
|
||||
[[proxies]]
|
||||
name = "{dongle_id}_auth"
|
||||
name = "{slug}_auth"
|
||||
type = "http"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = {AUTH_PORT}
|
||||
customDomains = ["auth-{dongle_id}.devices.local"]
|
||||
customDomains = ["auth-{slug}.devices.local"]
|
||||
"""
|
||||
frpc_toml.write_text(config)
|
||||
|
||||
print("Galaxy: Starting frpc tunnel...")
|
||||
print(f"Galaxy: Starting frpc tunnel (slug: {slug[:4]}...)...")
|
||||
log_file = open(FRPC_LOG, 'a')
|
||||
process = subprocess.Popen(
|
||||
[str(GALAXY_DIR / "frpc"), "-c", str(frpc_toml)],
|
||||
stdout=log_file,
|
||||
stderr=log_file
|
||||
)
|
||||
last_slug = slug
|
||||
else:
|
||||
if process is not None and process.poll() is None:
|
||||
print("Galaxy: Password cleared. Stopping frpc tunnel...")
|
||||
cleanup_frpc()
|
||||
last_slug = None
|
||||
|
||||
time.sleep(3)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
@@ -3210,10 +3210,11 @@ def setup(app):
|
||||
paired = GALAXY_AUTH_FILE.is_file() and len(GALAXY_AUTH_FILE.read_text().strip()) == 64
|
||||
except Exception:
|
||||
paired = False
|
||||
dongle_id = params.get("DongleId", encoding="utf8") or ""
|
||||
slug_file = GALAXY_DIR / "glxyslug"
|
||||
slug = slug_file.read_text().strip() if slug_file.is_file() else ""
|
||||
return jsonify({
|
||||
"paired": paired,
|
||||
"url": f"https://galaxy.firestar.link/{dongle_id}" if dongle_id else "",
|
||||
"url": f"https://galaxy.firestar.link/{slug}" if slug else "",
|
||||
})
|
||||
|
||||
@app.route("/api/galaxy/pair", methods=["POST"])
|
||||
@@ -3226,17 +3227,26 @@ def setup(app):
|
||||
pw_hash = hashlib.sha256(password.encode()).hexdigest()
|
||||
GALAXY_DIR.mkdir(parents=True, exist_ok=True)
|
||||
GALAXY_AUTH_FILE.write_text(pw_hash)
|
||||
|
||||
# Generate 256-bit secure session token
|
||||
(GALAXY_DIR / "glxysession").write_text(secrets.token_hex(32))
|
||||
|
||||
# Generate 16-character alphanumeric routing slug
|
||||
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
slug = ''.join(secrets.choice(charset) for _ in range(16))
|
||||
(GALAXY_DIR / "glxyslug").write_text(slug)
|
||||
|
||||
dongle_id = params.get("DongleId", encoding="utf8") or ""
|
||||
return jsonify({
|
||||
"message": "Pairing successful!",
|
||||
"url": f"https://galaxy.firestar.link/{dongle_id}" if dongle_id else "",
|
||||
"url": f"https://galaxy.firestar.link/{slug}",
|
||||
})
|
||||
|
||||
@app.route("/api/galaxy/unpair", methods=["POST"])
|
||||
def galaxy_unpair():
|
||||
if GALAXY_AUTH_FILE.is_file():
|
||||
GALAXY_AUTH_FILE.unlink()
|
||||
for f in ["glxyauth", "glxysession", "glxyslug"]:
|
||||
file_path = GALAXY_DIR / f
|
||||
if file_path.is_file():
|
||||
file_path.unlink()
|
||||
return jsonify({"message": "Galaxy unpaired successfully."})
|
||||
|
||||
@app.route("/api/tailscale/installed", methods=["GET"])
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <QDebug>
|
||||
#include <QCryptographicHash>
|
||||
#include <QRandomGenerator>
|
||||
#include <QrCode.hpp>
|
||||
|
||||
#include "common/watchdog.h"
|
||||
@@ -259,6 +260,16 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
|
||||
});
|
||||
addItem(pair_device);
|
||||
|
||||
// Shared helper to show QR popup
|
||||
auto showGalaxyQR = [=]() {
|
||||
std::string slug = util::read_file("/data/galaxy/glxyslug");
|
||||
if (slug.empty()) return;
|
||||
QString url = "https://galaxy.firestar.link/" + QString::fromStdString(slug);
|
||||
GalaxyQRPopup *popup = new GalaxyQRPopup(url, this);
|
||||
popup->exec();
|
||||
popup->deleteLater();
|
||||
};
|
||||
|
||||
pair_galaxy = new ButtonControl(tr("Galaxy"), tr("Pair"), tr("Pair your device with Galaxy for remote access to The Pond."));
|
||||
connect(pair_galaxy, &ButtonControl::clicked, [=]() {
|
||||
std::string current_password = util::read_file("/data/galaxy/glxyauth");
|
||||
@@ -268,32 +279,50 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
|
||||
std::string hash = QCryptographicHash::hash(new_password.toUtf8(), QCryptographicHash::Sha256).toHex().toStdString();
|
||||
util::create_directories("/data/galaxy", 0775);
|
||||
util::write_file("/data/galaxy/glxyauth", hash.data(), hash.size(), O_WRONLY | O_CREAT | O_TRUNC);
|
||||
|
||||
// Generate 256-bit session token (64 hex chars)
|
||||
QByteArray session_bytes(32, 0);
|
||||
QRandomGenerator::securelySeeded().fillRange(reinterpret_cast<quint32*>(session_bytes.data()), 8);
|
||||
std::string session_token = session_bytes.toHex().toStdString();
|
||||
util::write_file("/data/galaxy/glxysession", session_token.data(), session_token.size(), O_WRONLY | O_CREAT | O_TRUNC);
|
||||
|
||||
// Generate 16-char URL-safe slug (alphanumeric only)
|
||||
const char charset[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
std::string slug(16, '\0');
|
||||
auto *rng = QRandomGenerator::global();
|
||||
for (int i = 0; i < 16; i++) slug[i] = charset[rng->bounded(62)];
|
||||
util::write_file("/data/galaxy/glxyslug", slug.data(), slug.size(), O_WRONLY | O_CREAT | O_TRUNC);
|
||||
|
||||
pair_galaxy->setText(tr("Unpair"));
|
||||
ConfirmationDialog::alert(tr("Pairing successful! Visit galaxy.firestar.link/") + getDongleId().value_or("") + tr(" to connect."), this);
|
||||
galaxy_qr->setVisible(true);
|
||||
galaxy_qr_btn->setVisible(true);
|
||||
showGalaxyQR();
|
||||
}
|
||||
} else {
|
||||
if (ConfirmationDialog::confirm(tr("Are you sure you want to unpair from Galaxy?"), tr("Unpair"), this)) {
|
||||
std::remove("/data/galaxy/glxyauth");
|
||||
std::remove("/data/galaxy/glxysession");
|
||||
std::remove("/data/galaxy/glxyslug");
|
||||
pair_galaxy->setText(tr("Pair"));
|
||||
galaxy_qr->setVisible(false);
|
||||
galaxy_qr_btn->setVisible(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// QR button — sits inline next to the Pair/Unpair button
|
||||
galaxy_qr_btn = new QPushButton(tr("QR"));
|
||||
galaxy_qr_btn->setFixedSize(250, 100);
|
||||
galaxy_qr_btn->setStyleSheet(R"(
|
||||
QPushButton { background-color: #393939; color: #E4E4E4; border-radius: 50px; font-size: 35px; font-weight: 500; }
|
||||
QPushButton:pressed { background-color: #4a4a4a; }
|
||||
)");
|
||||
galaxy_qr_btn->setVisible(false);
|
||||
connect(galaxy_qr_btn, &QPushButton::clicked, showGalaxyQR);
|
||||
if (QHBoxLayout *hlayout = pair_galaxy->findChild<QHBoxLayout *>()) {
|
||||
hlayout->insertWidget(3, galaxy_qr_btn);
|
||||
}
|
||||
|
||||
addItem(pair_galaxy);
|
||||
|
||||
galaxy_qr = new ButtonControl(tr("Galaxy QR"), tr("SHOW"), tr("Show a QR code to quickly open Galaxy on your phone."));
|
||||
connect(galaxy_qr, &ButtonControl::clicked, [=]() {
|
||||
auto dongleId = getDongleId();
|
||||
if (!dongleId) return;
|
||||
|
||||
QString url = "https://galaxy.firestar.link/" + *dongleId;
|
||||
GalaxyQRPopup *popup = new GalaxyQRPopup(url, this);
|
||||
popup->exec();
|
||||
popup->deleteLater();
|
||||
});
|
||||
addItem(galaxy_qr);
|
||||
|
||||
// offroad-only buttons
|
||||
|
||||
auto dcamBtn = new ButtonControl(tr("Driver Camera"), tr("PREVIEW"),
|
||||
@@ -437,7 +466,7 @@ void DevicePanel::showEvent(QShowEvent *event) {
|
||||
std::string galaxy_pin = util::read_file("/data/galaxy/glxyauth");
|
||||
bool galaxy_paired = !galaxy_pin.empty();
|
||||
pair_galaxy->setText(galaxy_paired ? tr("Unpair") : tr("Pair"));
|
||||
galaxy_qr->setVisible(galaxy_paired);
|
||||
galaxy_qr_btn->setVisible(galaxy_paired);
|
||||
|
||||
ListWidget::showEvent(event);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ private:
|
||||
Params params;
|
||||
ButtonControl *pair_device;
|
||||
ButtonControl *pair_galaxy;
|
||||
ButtonControl *galaxy_qr;
|
||||
QPushButton *galaxy_qr_btn;
|
||||
};
|
||||
|
||||
class TogglesPanel : public ListWidget {
|
||||
|
||||
Reference in New Issue
Block a user