superbrain-server 1.0.6 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/start.py +62 -50
package/package.json
CHANGED
package/payload/start.py
CHANGED
|
@@ -666,7 +666,7 @@ LOCALTUNNEL_ENABLED = BASE_DIR / "config" / "localtunnel_enabled.txt"
|
|
|
666
666
|
LOCALTUNNEL_LOG = BASE_DIR / "config" / "localtunnel.log"
|
|
667
667
|
|
|
668
668
|
def setup_remote_access():
|
|
669
|
-
h1("Step 6 of 7 — Remote Access (
|
|
669
|
+
h1("Step 6 of 7 — Remote Access (localhost.run / Port Forwarding)")
|
|
670
670
|
|
|
671
671
|
print(f"""
|
|
672
672
|
The SuperBrain backend runs on {BOLD}port 5000{RESET} on your machine.
|
|
@@ -674,10 +674,10 @@ def setup_remote_access():
|
|
|
674
674
|
|
|
675
675
|
You have two options:
|
|
676
676
|
|
|
677
|
-
{BOLD}Option A —
|
|
678
|
-
|
|
677
|
+
{BOLD}Option A — localhost.run (easiest + free ssh tunnel){RESET}
|
|
678
|
+
localhost.run creates a public HTTPS URL seamlessly using SSH.
|
|
679
679
|
No account required.
|
|
680
|
-
Official site: {CYAN}https://
|
|
680
|
+
Official site: {CYAN}https://localhost.run/{RESET}
|
|
681
681
|
|
|
682
682
|
{BOLD}Option B — Your own port forwarding (advanced){RESET}
|
|
683
683
|
Forward {BOLD}TCP port 5000{RESET} on your router to your machine's local IP.
|
|
@@ -693,10 +693,10 @@ def setup_remote_access():
|
|
|
693
693
|
the same network. Use your PC's local IP (e.g. 192.168.x.x) in the app.{RESET}
|
|
694
694
|
""")
|
|
695
695
|
|
|
696
|
-
choice = ask_yn("Enable
|
|
696
|
+
choice = ask_yn("Enable localhost.run on startup?", default=True)
|
|
697
697
|
if not choice:
|
|
698
698
|
LOCALTUNNEL_ENABLED.unlink(missing_ok=True)
|
|
699
|
-
warn("Skipping
|
|
699
|
+
warn("Skipping localhost.run. Use either your own port forwarding or local WiFi.")
|
|
700
700
|
info("Remember: set the correct server URL in the mobile app Settings.")
|
|
701
701
|
return
|
|
702
702
|
|
|
@@ -711,15 +711,15 @@ def setup_remote_access():
|
|
|
711
711
|
|
|
712
712
|
After installing, re-run {BOLD}python start.py{RESET}.
|
|
713
713
|
""")
|
|
714
|
-
warn("Skipping
|
|
714
|
+
warn("Skipping localhost.run setup.")
|
|
715
715
|
return
|
|
716
716
|
|
|
717
717
|
ok("npx binary found")
|
|
718
718
|
LOCALTUNNEL_ENABLED.parent.mkdir(parents=True, exist_ok=True)
|
|
719
719
|
LOCALTUNNEL_ENABLED.write_text("enabled")
|
|
720
|
-
ok("
|
|
720
|
+
ok("localhost.run auto-start enabled")
|
|
721
721
|
nl()
|
|
722
|
-
info("
|
|
722
|
+
info("localhost.run will be started automatically every time you run start.py.")
|
|
723
723
|
|
|
724
724
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
725
725
|
# Step 6 — Access Token & Database
|
|
@@ -754,83 +754,95 @@ def setup_token_and_db():
|
|
|
754
754
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
755
755
|
# Launch Backend
|
|
756
756
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
757
|
-
def
|
|
758
|
-
"""Extract first
|
|
757
|
+
def _extract_localhost_run_url(text: str) -> str | None:
|
|
758
|
+
"""Extract first localhost.run public URL from text."""
|
|
759
759
|
import re
|
|
760
|
-
|
|
761
|
-
|
|
760
|
+
lines = text.splitlines()
|
|
761
|
+
for line in reversed(lines):
|
|
762
|
+
if "tunneled with tls termination" in line or ".lhr.life" in line or ".localhost.run" in line:
|
|
763
|
+
match = re.search(r'(https://[a-zA-Z0-9-]+\.(?:lhr\.life|localhost\.run))', line)
|
|
764
|
+
if match:
|
|
765
|
+
return match.group(1)
|
|
766
|
+
return None
|
|
762
767
|
|
|
763
768
|
|
|
764
|
-
def
|
|
769
|
+
def _find_localhost_run_url_from_log() -> str | None:
|
|
765
770
|
"""Read local tunnel log and return detected public URL if available."""
|
|
766
771
|
try:
|
|
767
772
|
if not LOCALTUNNEL_LOG.exists():
|
|
768
773
|
return None
|
|
769
774
|
text = LOCALTUNNEL_LOG.read_text(encoding="utf-8", errors="ignore")
|
|
770
|
-
return
|
|
775
|
+
return _extract_localhost_run_url(text)
|
|
771
776
|
except Exception:
|
|
772
777
|
return None
|
|
773
778
|
|
|
774
779
|
|
|
775
|
-
def
|
|
776
|
-
"""Stop existing
|
|
780
|
+
def _stop_localhost_run_processes():
|
|
781
|
+
"""Stop existing localhost.run processes so only one tunnel remains active."""
|
|
777
782
|
try:
|
|
778
783
|
if IS_WINDOWS:
|
|
779
784
|
script = (
|
|
780
785
|
"Get-CimInstance Win32_Process "
|
|
781
|
-
"| Where-Object { $_.CommandLine -match '
|
|
786
|
+
"| Where-Object { $_.CommandLine -match 'nokey@localhost.run' } "
|
|
782
787
|
"| ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }"
|
|
783
788
|
)
|
|
784
789
|
subprocess.run(["powershell", "-NoProfile", "-Command", script], check=False)
|
|
785
790
|
else:
|
|
786
|
-
subprocess.run(["pkill", "-f", "
|
|
791
|
+
subprocess.run(["pkill", "-f", "nokey@localhost.run"], check=False)
|
|
787
792
|
except Exception:
|
|
788
793
|
pass
|
|
789
794
|
|
|
790
795
|
|
|
791
|
-
def
|
|
792
|
-
"""Start
|
|
796
|
+
def _start_localhost_run(port: int, timeout: int = 25) -> str | None:
|
|
797
|
+
"""Start localhost.run in the background via SSH and wait for the public URL."""
|
|
793
798
|
import time as _time
|
|
794
799
|
|
|
795
|
-
|
|
796
|
-
if not
|
|
800
|
+
ssh_exec = shutil.which("ssh")
|
|
801
|
+
if not ssh_exec:
|
|
802
|
+
warn("SSH is required to use localhost.run. Please install OpenSSH.")
|
|
797
803
|
return None
|
|
798
804
|
|
|
799
|
-
# Clean stale
|
|
800
|
-
|
|
805
|
+
# Clean stale ssh tunnel processes.
|
|
806
|
+
_stop_localhost_run_processes()
|
|
801
807
|
_time.sleep(0.8)
|
|
802
808
|
|
|
803
|
-
info("Starting
|
|
809
|
+
info("Starting localhost.run SSH tunnel in background …")
|
|
804
810
|
try:
|
|
805
811
|
LOCALTUNNEL_LOG.parent.mkdir(parents=True, exist_ok=True)
|
|
806
812
|
LOCALTUNNEL_LOG.write_text("")
|
|
807
813
|
|
|
808
814
|
log_handle = open(LOCALTUNNEL_LOG, "a", encoding="utf-8", buffering=1)
|
|
809
815
|
kwargs = {
|
|
810
|
-
"start_new_session": True,
|
|
811
816
|
"stdout": log_handle,
|
|
812
817
|
"stderr": subprocess.STDOUT,
|
|
818
|
+
"stdin": subprocess.DEVNULL,
|
|
813
819
|
"text": True,
|
|
814
820
|
}
|
|
815
|
-
if IS_WINDOWS
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
subprocess.Popen(cmd, **kwargs)
|
|
821
|
+
if IS_WINDOWS:
|
|
822
|
+
kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
|
|
823
|
+
|
|
824
|
+
cmd = [ssh_exec, "-o", "StrictHostKeyChecking=no", "-R", f"80:localhost:{port}", "nokey@localhost.run"]
|
|
825
|
+
process = subprocess.Popen(cmd, **kwargs)
|
|
826
|
+
|
|
827
|
+
_time.sleep(1.0)
|
|
828
|
+
if process.poll() is not None:
|
|
829
|
+
warn(f"Could not start localhost.run, exited with {process.returncode}")
|
|
830
|
+
return None
|
|
831
|
+
|
|
820
832
|
except Exception as e:
|
|
821
|
-
warn(f"Could not start
|
|
833
|
+
warn(f"Could not start localhost.run: {e}")
|
|
822
834
|
return None
|
|
823
835
|
|
|
824
836
|
# Poll log output until URL is emitted.
|
|
825
837
|
deadline = _time.time() + timeout
|
|
826
838
|
while _time.time() < deadline:
|
|
827
839
|
_time.sleep(1)
|
|
828
|
-
url =
|
|
840
|
+
url = _find_localhost_run_url_from_log()
|
|
829
841
|
if url:
|
|
830
|
-
ok(f"
|
|
842
|
+
ok(f"localhost.run active → {GREEN}{BOLD}{url}{RESET}")
|
|
831
843
|
return url
|
|
832
844
|
|
|
833
|
-
warn("
|
|
845
|
+
warn("localhost.run started but URL is not available yet.")
|
|
834
846
|
info(f"Check tunnel logs in: {LOCALTUNNEL_LOG}")
|
|
835
847
|
return None
|
|
836
848
|
|
|
@@ -1202,26 +1214,26 @@ def launch_backend():
|
|
|
1202
1214
|
token = TOKEN_FILE.read_text().strip() if TOKEN_FILE.exists() else "—"
|
|
1203
1215
|
local_ip = _detect_local_ip()
|
|
1204
1216
|
|
|
1205
|
-
|
|
1217
|
+
localhost_run_enabled = bool(shutil.which("ssh"))
|
|
1206
1218
|
|
|
1207
|
-
|
|
1208
|
-
if
|
|
1209
|
-
|
|
1219
|
+
localhost_run_url: str | None = None
|
|
1220
|
+
if localhost_run_enabled:
|
|
1221
|
+
localhost_run_url = _start_localhost_run(PORT)
|
|
1210
1222
|
else:
|
|
1211
|
-
|
|
1223
|
+
localhost_run_url = _find_localhost_run_url_from_log()
|
|
1212
1224
|
|
|
1213
|
-
if
|
|
1214
|
-
tunnel_line = f" Public URL → {GREEN}{BOLD}{
|
|
1215
|
-
tunnel_hint = f" · public → {GREEN}{
|
|
1216
|
-
elif
|
|
1225
|
+
if localhost_run_url:
|
|
1226
|
+
tunnel_line = f" Public URL → {GREEN}{BOLD}{localhost_run_url}{RESET} {DIM}(localhost.run){RESET}"
|
|
1227
|
+
tunnel_hint = f" · public → {GREEN}{localhost_run_url}{RESET}"
|
|
1228
|
+
elif localhost_run_enabled:
|
|
1217
1229
|
tunnel_line = f" Public URL → {YELLOW}(starting — URL pending, check localtunnel.log){RESET}"
|
|
1218
|
-
tunnel_hint = f" · public → run: {DIM}
|
|
1230
|
+
tunnel_hint = f" · public → run: {DIM}ssh -R 80:localhost:{PORT} nokey@localhost.run{RESET}"
|
|
1219
1231
|
else:
|
|
1220
1232
|
tunnel_line = ""
|
|
1221
|
-
tunnel_hint = f" · public → install
|
|
1233
|
+
tunnel_hint = f" · public → install OpenSSH first, then run: {DIM}ssh -R 80:localhost:{PORT} nokey@localhost.run{RESET}"
|
|
1222
1234
|
|
|
1223
1235
|
# ── Generate and display QR code ──────────────────────────────────────────
|
|
1224
|
-
qr_url =
|
|
1236
|
+
qr_url = localhost_run_url if localhost_run_url else f"http://{local_ip}:{PORT}"
|
|
1225
1237
|
_display_connect_qr(qr_url, token)
|
|
1226
1238
|
|
|
1227
1239
|
print(f"""
|
|
@@ -1282,7 +1294,7 @@ def launch_backend_status():
|
|
|
1282
1294
|
url = match.group(1)
|
|
1283
1295
|
|
|
1284
1296
|
if url == "NOT_FOUND":
|
|
1285
|
-
warn("Could not find a running
|
|
1297
|
+
warn("Could not find a running localhost.run URL in config/localtunnel.log.")
|
|
1286
1298
|
nl()
|
|
1287
1299
|
print(" Wait 5 seconds, or run 'superbrain-server' to start the server.")
|
|
1288
1300
|
return
|
|
@@ -1327,7 +1339,7 @@ def main():
|
|
|
1327
1339
|
3 · Configure AI provider keys + Instagram credentials
|
|
1328
1340
|
4 · Set up an offline AI model via Ollama (qwen3-vl:4b)
|
|
1329
1341
|
5 · Set up offline audio transcription (Whisper + ffmpeg)
|
|
1330
|
-
6 · Configure remote access (
|
|
1342
|
+
6 · Configure remote access (localhost.run or port forwarding)
|
|
1331
1343
|
7 · Generate Access Token & initialise database
|
|
1332
1344
|
|
|
1333
1345
|
Press {BOLD}Enter{RESET} to accept defaults shown in [{DIM}brackets{RESET}].
|