superbrain-server 1.0.2-beta.0
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/bin/superbrain.js +196 -0
- package/package.json +23 -0
- package/payload/.dockerignore +45 -0
- package/payload/.env.example +58 -0
- package/payload/Dockerfile +73 -0
- package/payload/analyzers/__init__.py +0 -0
- package/payload/analyzers/audio_transcribe.py +225 -0
- package/payload/analyzers/caption.py +244 -0
- package/payload/analyzers/music_identifier.py +346 -0
- package/payload/analyzers/text_analyzer.py +117 -0
- package/payload/analyzers/visual_analyze.py +218 -0
- package/payload/analyzers/webpage_analyzer.py +789 -0
- package/payload/analyzers/youtube_analyzer.py +320 -0
- package/payload/api.py +1676 -0
- package/payload/config/.api_keys.example +22 -0
- package/payload/config/model_rankings.json +492 -0
- package/payload/config/openrouter_free_models.json +1364 -0
- package/payload/config/whisper_model.txt +1 -0
- package/payload/config_settings.py +185 -0
- package/payload/core/__init__.py +0 -0
- package/payload/core/category_manager.py +219 -0
- package/payload/core/database.py +811 -0
- package/payload/core/link_checker.py +300 -0
- package/payload/core/model_router.py +1253 -0
- package/payload/docker-compose.yml +120 -0
- package/payload/instagram/__init__.py +0 -0
- package/payload/instagram/instagram_downloader.py +253 -0
- package/payload/instagram/instagram_login.py +190 -0
- package/payload/main.py +912 -0
- package/payload/requirements.txt +39 -0
- package/payload/reset.py +311 -0
- package/payload/start-docker-prod.sh +125 -0
- package/payload/start-docker.sh +56 -0
- package/payload/start.py +1302 -0
- package/payload/static/favicon.ico +0 -0
- package/payload/stop-docker.sh +16 -0
- package/payload/utils/__init__.py +0 -0
- package/payload/utils/db_stats.py +108 -0
- package/payload/utils/manage_token.py +91 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# ── Web Framework ─────────────────────────────────────────────────────────────
|
|
2
|
+
fastapi>=0.111.0
|
|
3
|
+
uvicorn[standard]>=0.29.0
|
|
4
|
+
pydantic>=2.0.0
|
|
5
|
+
python-multipart>=0.0.9
|
|
6
|
+
|
|
7
|
+
# ── HTTP ──────────────────────────────────────────────────────────────────────
|
|
8
|
+
requests>=2.31.0
|
|
9
|
+
httpx>=0.27.0
|
|
10
|
+
|
|
11
|
+
# ── AI Providers ──────────────────────────────────────────────────────────────
|
|
12
|
+
groq>=0.9.0
|
|
13
|
+
google-genai>=0.8.0
|
|
14
|
+
|
|
15
|
+
# ── Web Page Fetching & Parsing ───────────────────────────────────────────────
|
|
16
|
+
beautifulsoup4>=4.12.0
|
|
17
|
+
trafilatura>=1.12.0
|
|
18
|
+
newspaper4k>=0.9.0
|
|
19
|
+
lxml>=5.0.0
|
|
20
|
+
lxml_html_clean>=0.1.0
|
|
21
|
+
htmldate>=1.9.0
|
|
22
|
+
|
|
23
|
+
# ── Audio Transcription ───────────────────────────────────────────────────────
|
|
24
|
+
openai-whisper>=20231117
|
|
25
|
+
|
|
26
|
+
# ── Music Identification ──────────────────────────────────────────────────────
|
|
27
|
+
shazamio>=0.4.0
|
|
28
|
+
|
|
29
|
+
# ── Instagram Download ────────────────────────────────────────────────────────
|
|
30
|
+
instaloader>=4.11.0
|
|
31
|
+
instagrapi>=2.0.0
|
|
32
|
+
|
|
33
|
+
# ── Computer Vision ───────────────────────────────────────────────────────────
|
|
34
|
+
opencv-python-headless>=4.9.0.80
|
|
35
|
+
Pillow>=10.0.0
|
|
36
|
+
|
|
37
|
+
# ── Terminal UI ───────────────────────────────────────────────────────────────
|
|
38
|
+
rich>=13.0.0
|
|
39
|
+
segno>=1.6.0
|
package/payload/reset.py
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
╔══════════════════════════════════════════════════════════════════╗
|
|
4
|
+
║ SuperBrain — Reset / Clean Utility ║
|
|
5
|
+
║ Selectively wipe configuration, data, and environment. ║
|
|
6
|
+
╚══════════════════════════════════════════════════════════════════╝
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python reset.py — interactive reset wizard
|
|
10
|
+
python reset.py --all — full wipe without prompting (⚠ destructive)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
import os
|
|
15
|
+
import shutil
|
|
16
|
+
import platform
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
# ── Paths ─────────────────────────────────────────────────────────────────────
|
|
20
|
+
BASE_DIR = Path(__file__).parent.resolve()
|
|
21
|
+
VENV_DIR = BASE_DIR / ".venv"
|
|
22
|
+
API_KEYS = BASE_DIR / "config" / ".api_keys"
|
|
23
|
+
LOCALTUNNEL_ENABLED = BASE_DIR / "config" / "localtunnel_enabled.txt"
|
|
24
|
+
LOCALTUNNEL_LOG = BASE_DIR / "config" / "localtunnel.log"
|
|
25
|
+
TOKEN_FILE = BASE_DIR / "token.txt"
|
|
26
|
+
SETUP_DONE = BASE_DIR / ".setup_done"
|
|
27
|
+
DB_FILE = BASE_DIR / "superbrain.db"
|
|
28
|
+
TEMP_DIR = BASE_DIR / "temp"
|
|
29
|
+
INSTA_SESS = BASE_DIR / "config" / "instagram_session.json"
|
|
30
|
+
|
|
31
|
+
IS_WINDOWS = platform.system() == "Windows"
|
|
32
|
+
|
|
33
|
+
# ── ANSI colours ──────────────────────────────────────────────────────────────
|
|
34
|
+
def _ansi(code): return f"\033[{code}m"
|
|
35
|
+
RESET = _ansi(0); BOLD = _ansi(1)
|
|
36
|
+
RED = _ansi(31); GREEN = _ansi(32); YELLOW = _ansi(33)
|
|
37
|
+
BLUE = _ansi(34); CYAN = _ansi(36)
|
|
38
|
+
DIM = _ansi(2); MAG = _ansi(35)
|
|
39
|
+
|
|
40
|
+
def link(url: str, text: str | None = None) -> str:
|
|
41
|
+
label = text or url
|
|
42
|
+
return f"\033]8;;{url}\033\\{label}\033]8;;\033\\"
|
|
43
|
+
|
|
44
|
+
def banner():
|
|
45
|
+
art = f"""{CYAN}{BOLD}
|
|
46
|
+
███████╗██╗ ██╗██████╗ ███████╗██████╗
|
|
47
|
+
██╔════╝██║ ██║██╔══██╗██╔════╝██╔══██╗
|
|
48
|
+
███████╗██║ ██║██████╔╝█████╗ ██████╔╝
|
|
49
|
+
╚════██║██║ ██║██╔═══╝ ██╔══╝ ██╔══██╗
|
|
50
|
+
███████║╚██████╔╝██║ ███████╗██║ ██║
|
|
51
|
+
╚══════╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
|
|
52
|
+
|
|
53
|
+
██████╗ ██████╗ █████╗ ██╗███╗ ██╗
|
|
54
|
+
██╔══██╗██╔══██╗██╔══██╗██║████╗ ██║
|
|
55
|
+
██████╔╝██████╔╝███████║██║██╔██╗ ██║
|
|
56
|
+
██╔══██╗██╔══██╗██╔══██║██║██║╚██╗██║
|
|
57
|
+
██████╔╝██║ ██║██║ ██║██║██║ ╚████║
|
|
58
|
+
╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝
|
|
59
|
+
{RESET}"""
|
|
60
|
+
credit = (f" {DIM}made with {RESET}{MAG}❤{RESET}{DIM} by "
|
|
61
|
+
f"{link('https://github.com/sidinsearch', f'{BOLD}sidinsearch{RESET}{DIM}')}"
|
|
62
|
+
f"{RESET}\n")
|
|
63
|
+
print(art + credit)
|
|
64
|
+
|
|
65
|
+
def h1(msg): print(f"\n{BOLD}{CYAN}{'━'*64}{RESET}\n{BOLD} {msg}{RESET}\n{BOLD}{CYAN}{'━'*64}{RESET}")
|
|
66
|
+
def ok(msg): print(f" {GREEN}✓{RESET} {msg}")
|
|
67
|
+
def warn(msg): print(f" {YELLOW}⚠{RESET} {msg}")
|
|
68
|
+
def err(msg): print(f" {RED}✗{RESET} {msg}")
|
|
69
|
+
def info(msg): print(f" {DIM}{msg}{RESET}")
|
|
70
|
+
def nl(): print()
|
|
71
|
+
|
|
72
|
+
def ask_yn(prompt: str, default: bool = True) -> bool:
|
|
73
|
+
hint = f"{BOLD}Y{RESET}/n" if default else f"y/{BOLD}N{RESET}"
|
|
74
|
+
val = input(f"\n {BOLD}{prompt}{RESET} [{hint}]: ").strip().lower()
|
|
75
|
+
if val == "":
|
|
76
|
+
return default
|
|
77
|
+
return val in ("y", "yes")
|
|
78
|
+
|
|
79
|
+
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
80
|
+
def _remove_file(path: Path, label: str):
|
|
81
|
+
"""Delete a single file (or glob of siblings like .db-shm / .db-wal)."""
|
|
82
|
+
targets = list(path.parent.glob(path.name + "*")) if not path.exists() else [path]
|
|
83
|
+
targets += list(path.parent.glob(path.name + "-shm"))
|
|
84
|
+
targets += list(path.parent.glob(path.name + "-wal"))
|
|
85
|
+
removed = False
|
|
86
|
+
for t in dict.fromkeys(targets): # deduplicate, preserve order
|
|
87
|
+
if t.exists():
|
|
88
|
+
t.unlink()
|
|
89
|
+
removed = True
|
|
90
|
+
if removed:
|
|
91
|
+
ok(f"{label} deleted")
|
|
92
|
+
else:
|
|
93
|
+
info(f"{label} — nothing to delete")
|
|
94
|
+
|
|
95
|
+
def _remove_dir(path: Path, label: str):
|
|
96
|
+
if path.exists():
|
|
97
|
+
shutil.rmtree(path)
|
|
98
|
+
ok(f"{label} deleted")
|
|
99
|
+
else:
|
|
100
|
+
info(f"{label} — nothing to delete")
|
|
101
|
+
|
|
102
|
+
# ── Reset actions ─────────────────────────────────────────────────────────────
|
|
103
|
+
def reset_api_keys():
|
|
104
|
+
h1("Reset — API Keys")
|
|
105
|
+
warn("This removes ALL keys: Gemini / Groq / OpenRouter and Instagram credentials.")
|
|
106
|
+
if not ask_yn("Continue?", default=False):
|
|
107
|
+
info("Skipped.")
|
|
108
|
+
return
|
|
109
|
+
_remove_file(API_KEYS, "API keys file (config/.api_keys)")
|
|
110
|
+
ok("Run python start.py --reset to re-enter keys.")
|
|
111
|
+
|
|
112
|
+
def reset_localtunnel():
|
|
113
|
+
h1("Reset — localtunnel State")
|
|
114
|
+
warn("This removes localtunnel startup state and cached log output.")
|
|
115
|
+
if not ask_yn("Continue?", default=False):
|
|
116
|
+
info("Skipped.")
|
|
117
|
+
return
|
|
118
|
+
_remove_file(LOCALTUNNEL_ENABLED, "localtunnel state (config/localtunnel_enabled.txt)")
|
|
119
|
+
_remove_file(LOCALTUNNEL_LOG, "localtunnel log (config/localtunnel.log)")
|
|
120
|
+
|
|
121
|
+
def reset_api_token():
|
|
122
|
+
h1("Reset — Access Token")
|
|
123
|
+
warn("All mobile devices will lose access until you update the token in their Settings.")
|
|
124
|
+
if not ask_yn("Continue?", default=False):
|
|
125
|
+
info("Skipped.")
|
|
126
|
+
return
|
|
127
|
+
_remove_file(TOKEN_FILE, "Access Token (token.txt)")
|
|
128
|
+
ok("A new token will be generated next time you run python start.py.")
|
|
129
|
+
|
|
130
|
+
def reset_database():
|
|
131
|
+
h1("Reset — Database")
|
|
132
|
+
warn("This permanently deletes ALL saved posts, collections, and analysis data.")
|
|
133
|
+
warn("This action CANNOT be undone.")
|
|
134
|
+
nl()
|
|
135
|
+
confirm = input(f" Type {BOLD}DELETE{RESET} to confirm: ").strip()
|
|
136
|
+
if confirm != "DELETE":
|
|
137
|
+
info("Skipped.")
|
|
138
|
+
return
|
|
139
|
+
for pat in ["superbrain.db", "superbrain.db-shm", "superbrain.db-wal"]:
|
|
140
|
+
_remove_file(BASE_DIR / pat, pat)
|
|
141
|
+
ok("Database wiped. A fresh one will be created on next server start.")
|
|
142
|
+
|
|
143
|
+
def reset_temp():
|
|
144
|
+
h1("Reset — Temporary Files")
|
|
145
|
+
info("Removes downloaded media in the temp/ folder.")
|
|
146
|
+
if not ask_yn("Continue?", default=True):
|
|
147
|
+
info("Skipped.")
|
|
148
|
+
return
|
|
149
|
+
_remove_dir(TEMP_DIR, "temp/ folder")
|
|
150
|
+
|
|
151
|
+
def reset_instagram_session():
|
|
152
|
+
h1("Reset — Instagram Session")
|
|
153
|
+
info("Forces a fresh login to Instagram on the next request.")
|
|
154
|
+
if not ask_yn("Continue?", default=True):
|
|
155
|
+
info("Skipped.")
|
|
156
|
+
return
|
|
157
|
+
_remove_file(INSTA_SESS, "Instagram session file")
|
|
158
|
+
|
|
159
|
+
def reset_venv():
|
|
160
|
+
h1("Reset — Virtual Environment")
|
|
161
|
+
warn("This deletes the entire .venv/ folder.")
|
|
162
|
+
warn("You will need to run python start.py again to reinstall all packages.")
|
|
163
|
+
if not ask_yn("Continue?", default=False):
|
|
164
|
+
info("Skipped.")
|
|
165
|
+
return
|
|
166
|
+
_remove_dir(VENV_DIR, ".venv/ (virtual environment)")
|
|
167
|
+
|
|
168
|
+
def reset_setup_flag():
|
|
169
|
+
"""Always called as part of full reset — re-triggers the start.py wizard."""
|
|
170
|
+
_remove_file(SETUP_DONE, "setup flag (.setup_done)")
|
|
171
|
+
|
|
172
|
+
def export_database():
|
|
173
|
+
h1("Backup — Export Database")
|
|
174
|
+
info("This creates a safe backup of your database without deleting anything.")
|
|
175
|
+
|
|
176
|
+
if not DB_FILE.exists():
|
|
177
|
+
warn("No database found — nothing to export.")
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
import shutil
|
|
181
|
+
from datetime import datetime
|
|
182
|
+
|
|
183
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
184
|
+
backup_name = f"superbrain_backup_{timestamp}.db"
|
|
185
|
+
backup_path = BASE_DIR / backup_name
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
shutil.copy2(DB_FILE, backup_path)
|
|
189
|
+
ok(f"Database backed up to: {BOLD}{backup_name}{RESET}")
|
|
190
|
+
info(f"Size: {backup_path.stat().st_size / (1024*1024):.1f} MB")
|
|
191
|
+
info(f"Restore by copying this file back to: {BOLD}superbrain.db{RESET}")
|
|
192
|
+
except Exception as e:
|
|
193
|
+
err(f"Backup failed: {e}")
|
|
194
|
+
|
|
195
|
+
def full_reset():
|
|
196
|
+
h1("Full Reset — Wipe Everything")
|
|
197
|
+
nl()
|
|
198
|
+
print(f" This will delete:")
|
|
199
|
+
print(f" {RED}·{RESET} API keys (config/.api_keys)")
|
|
200
|
+
print(f" {RED}·{RESET} localtunnel state/log (config/localtunnel_*)")
|
|
201
|
+
print(f" {RED}·{RESET} Access Token (token.txt)")
|
|
202
|
+
print(f" {RED}·{RESET} Database (superbrain.db)")
|
|
203
|
+
print(f" {RED}·{RESET} Temporary media files (temp/)")
|
|
204
|
+
print(f" {RED}·{RESET} Instagram session")
|
|
205
|
+
print(f" {RED}·{RESET} Virtual environment (.venv/)")
|
|
206
|
+
print(f" {RED}·{RESET} Setup completion flag (.setup_done)")
|
|
207
|
+
nl()
|
|
208
|
+
warn("ALL DATA WILL BE LOST. This cannot be undone.")
|
|
209
|
+
nl()
|
|
210
|
+
confirm = input(f" Type {BOLD}RESET ALL{RESET} to confirm: ").strip()
|
|
211
|
+
if confirm != "RESET ALL":
|
|
212
|
+
info("Cancelled — nothing was deleted.")
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
for path, label in [
|
|
216
|
+
(API_KEYS, "API keys"),
|
|
217
|
+
(LOCALTUNNEL_ENABLED, "localtunnel state"),
|
|
218
|
+
(LOCALTUNNEL_LOG, "localtunnel log"),
|
|
219
|
+
(TOKEN_FILE, "Access Token"),
|
|
220
|
+
]:
|
|
221
|
+
_remove_file(path, label)
|
|
222
|
+
|
|
223
|
+
for pat in ["superbrain.db", "superbrain.db-shm", "superbrain.db-wal"]:
|
|
224
|
+
_remove_file(BASE_DIR / pat, pat)
|
|
225
|
+
|
|
226
|
+
_remove_file(INSTA_SESS, "Instagram session")
|
|
227
|
+
_remove_dir(TEMP_DIR, "temp/")
|
|
228
|
+
_remove_dir(VENV_DIR, ".venv/")
|
|
229
|
+
reset_setup_flag()
|
|
230
|
+
|
|
231
|
+
nl()
|
|
232
|
+
ok("Full reset complete.")
|
|
233
|
+
ok("Run python start.py to go through the setup wizard again.")
|
|
234
|
+
|
|
235
|
+
# ── Interactive menu ──────────────────────────────────────────────────────────
|
|
236
|
+
MENU_ITEMS = [
|
|
237
|
+
("1", "API Keys (config/.api_keys) — all keys + Instagram"),
|
|
238
|
+
("2", "localtunnel state (config/localtunnel_enabled.txt + .log)"),
|
|
239
|
+
("3", "Access Token (token.txt)"),
|
|
240
|
+
("4", "Database (superbrain.db) ⚠ all posts & collections"),
|
|
241
|
+
("5", "Temporary Files (temp/)"),
|
|
242
|
+
("6", "Instagram Session (force fresh login)"),
|
|
243
|
+
("7", "Virtual Environment (.venv/) ⚠ must reinstall packages"),
|
|
244
|
+
("8", "✓ Backup Database (create timestamped backup)"),
|
|
245
|
+
("9", f"{RED}{BOLD}Full Reset{RESET} — wipe everything listed above"),
|
|
246
|
+
("q", "Quit"),
|
|
247
|
+
]
|
|
248
|
+
|
|
249
|
+
ACTIONS = {
|
|
250
|
+
"1": reset_api_keys,
|
|
251
|
+
"2": reset_localtunnel,
|
|
252
|
+
"3": reset_api_token,
|
|
253
|
+
"4": reset_database,
|
|
254
|
+
"5": reset_temp,
|
|
255
|
+
"6": reset_instagram_session,
|
|
256
|
+
"7": reset_venv,
|
|
257
|
+
"8": export_database,
|
|
258
|
+
"9": full_reset,
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
def menu():
|
|
262
|
+
h1("SuperBrain Reset Utility")
|
|
263
|
+
nl()
|
|
264
|
+
print(f" {DIM}Select what you want to reset:{RESET}")
|
|
265
|
+
nl()
|
|
266
|
+
for key, label in MENU_ITEMS:
|
|
267
|
+
if key == "q":
|
|
268
|
+
print(f" {DIM}{key}{RESET} {DIM}{label}{RESET}")
|
|
269
|
+
else:
|
|
270
|
+
print(f" {BOLD}{key}{RESET} {label}")
|
|
271
|
+
nl()
|
|
272
|
+
choice = input(f" {BOLD}Choose [1-9 / q]{RESET}: ").strip().lower()
|
|
273
|
+
return choice
|
|
274
|
+
|
|
275
|
+
def main():
|
|
276
|
+
banner()
|
|
277
|
+
|
|
278
|
+
# --all shortcut: skip menu and wipe everything
|
|
279
|
+
if "--all" in sys.argv:
|
|
280
|
+
full_reset()
|
|
281
|
+
return
|
|
282
|
+
|
|
283
|
+
while True:
|
|
284
|
+
choice = menu()
|
|
285
|
+
if choice == "q" or choice == "":
|
|
286
|
+
nl()
|
|
287
|
+
info("Nothing changed. Goodbye!")
|
|
288
|
+
nl()
|
|
289
|
+
sys.exit(0)
|
|
290
|
+
|
|
291
|
+
action = ACTIONS.get(choice)
|
|
292
|
+
if action:
|
|
293
|
+
action()
|
|
294
|
+
nl()
|
|
295
|
+
again = ask_yn("Reset something else?", default=False)
|
|
296
|
+
if not again:
|
|
297
|
+
nl()
|
|
298
|
+
info("Done. Run python start.py to start the server.")
|
|
299
|
+
nl()
|
|
300
|
+
break
|
|
301
|
+
else:
|
|
302
|
+
warn(f"Unknown option '{choice}' — try again.")
|
|
303
|
+
|
|
304
|
+
if __name__ == "__main__":
|
|
305
|
+
try:
|
|
306
|
+
main()
|
|
307
|
+
except KeyboardInterrupt:
|
|
308
|
+
nl()
|
|
309
|
+
info("Interrupted. Nothing was changed.")
|
|
310
|
+
nl()
|
|
311
|
+
sys.exit(0)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# ────────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
# SuperBrain Backend Production Startup Script
|
|
5
|
+
# Ensures all configuration is validated and services are healthy
|
|
6
|
+
# ────────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
set -e # Exit on any error
|
|
9
|
+
|
|
10
|
+
# Colors for output
|
|
11
|
+
RED='\033[0;31m'
|
|
12
|
+
GREEN='\033[0;32m'
|
|
13
|
+
YELLOW='\033[1;33m'
|
|
14
|
+
BLUE='\033[0;34m'
|
|
15
|
+
NC='\033[0m' # No Color
|
|
16
|
+
|
|
17
|
+
echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}"
|
|
18
|
+
echo -e "${BLUE}║ SuperBrain API - Production Startup ║${NC}"
|
|
19
|
+
echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}"
|
|
20
|
+
|
|
21
|
+
# ──── Check prerequisites ────
|
|
22
|
+
echo -e "\n${YELLOW}→ Checking prerequisites...${NC}"
|
|
23
|
+
|
|
24
|
+
if ! command -v docker &> /dev/null; then
|
|
25
|
+
echo -e "${RED}✗ Docker is not installed${NC}"
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
echo -e "${GREEN}✓ Docker found${NC}"
|
|
29
|
+
|
|
30
|
+
if docker compose version &> /dev/null; then
|
|
31
|
+
COMPOSE_CMD="docker compose"
|
|
32
|
+
elif command -v docker-compose &> /dev/null; then
|
|
33
|
+
COMPOSE_CMD="docker-compose"
|
|
34
|
+
else
|
|
35
|
+
echo -e "${RED}✗ Docker Compose is not installed${NC}"
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
echo -e "${GREEN}✓ Docker Compose found (${COMPOSE_CMD})${NC}"
|
|
39
|
+
|
|
40
|
+
# ──── Check environment file ────
|
|
41
|
+
echo -e "\n${YELLOW}→ Checking environment configuration...${NC}"
|
|
42
|
+
|
|
43
|
+
if [ ! -f .env ]; then
|
|
44
|
+
if [ -f .env.example ]; then
|
|
45
|
+
echo -e "${YELLOW}⚠ .env file not found. Creating from .env.example...${NC}"
|
|
46
|
+
cp .env.example .env
|
|
47
|
+
echo -e "${YELLOW}⚠ Please edit .env with your actual credentials and run this script again${NC}"
|
|
48
|
+
exit 1
|
|
49
|
+
else
|
|
50
|
+
echo -e "${RED}✗ Neither .env nor .env.example found${NC}"
|
|
51
|
+
exit 1
|
|
52
|
+
fi
|
|
53
|
+
else
|
|
54
|
+
echo -e "${GREEN}✓ .env file found${NC}"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# ──── Check required API keys ────
|
|
58
|
+
source .env
|
|
59
|
+
|
|
60
|
+
if [ -z "$GROQ_API_KEY" ] && [ -z "$GEMINI_API_KEY" ] && [ -z "$GOOGLE_API_KEY" ] && [ -z "$OPENROUTER_API_KEY" ]; then
|
|
61
|
+
echo -e "${YELLOW}⚠ Warning: No AI provider API keys configured${NC}"
|
|
62
|
+
echo -e "${YELLOW} Set at least one of: GROQ_API_KEY, GEMINI_API_KEY, OPENROUTER_API_KEY${NC}"
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# ──── Create necessary directories ────
|
|
66
|
+
echo -e "\n${YELLOW}→ Setting up directories...${NC}"
|
|
67
|
+
|
|
68
|
+
mkdir -p config temp static/uploads logs
|
|
69
|
+
chmod 755 config temp static static/uploads logs
|
|
70
|
+
|
|
71
|
+
echo -e "${GREEN}✓ Directories created${NC}"
|
|
72
|
+
|
|
73
|
+
# ──── Build Docker image ────
|
|
74
|
+
echo -e "\n${YELLOW}→ Building Docker image...${NC}"
|
|
75
|
+
|
|
76
|
+
${COMPOSE_CMD} build --no-cache
|
|
77
|
+
|
|
78
|
+
echo -e "${GREEN}✓ Docker image built successfully${NC}"
|
|
79
|
+
|
|
80
|
+
# ──── Start services ────
|
|
81
|
+
echo -e "\n${YELLOW}→ Starting SuperBrain API...${NC}"
|
|
82
|
+
|
|
83
|
+
${COMPOSE_CMD} up -d
|
|
84
|
+
|
|
85
|
+
echo -e "${GREEN}✓ Containers started${NC}"
|
|
86
|
+
|
|
87
|
+
# ──── Wait for service to be healthy ────
|
|
88
|
+
echo -e "\n${YELLOW}→ Waiting for API to become healthy...${NC}"
|
|
89
|
+
|
|
90
|
+
for i in {1..30}; do
|
|
91
|
+
if ${COMPOSE_CMD} exec -T superbrain-api curl -s http://localhost:5000/health > /dev/null 2>&1; then
|
|
92
|
+
echo -e "${GREEN}✓ API is healthy${NC}"
|
|
93
|
+
break
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
if [ $i -eq 30 ]; then
|
|
97
|
+
echo -e "${RED}✗ API failed to start after 30 seconds${NC}"
|
|
98
|
+
${COMPOSE_CMD} logs superbrain-api
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
echo -n "."
|
|
103
|
+
sleep 1
|
|
104
|
+
done
|
|
105
|
+
|
|
106
|
+
# ──── Display status ────
|
|
107
|
+
echo -e "\n${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}"
|
|
108
|
+
echo -e "${BLUE}║ Startup Complete! ✓ ║${NC}"
|
|
109
|
+
echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}"
|
|
110
|
+
|
|
111
|
+
echo -e "\n${GREEN}Services running:${NC}"
|
|
112
|
+
${COMPOSE_CMD} ps
|
|
113
|
+
|
|
114
|
+
echo -e "\n${GREEN}API Information:${NC}"
|
|
115
|
+
echo -e " 📡 Endpoint: http://localhost:${API_PORT:-5000}"
|
|
116
|
+
echo -e " 📚 Documentation: http://localhost:${API_PORT:-5000}/docs"
|
|
117
|
+
echo -e " 🏥 Health Check: http://localhost:${API_PORT:-5000}/health"
|
|
118
|
+
|
|
119
|
+
echo -e "\n${GREEN}Useful commands:${NC}"
|
|
120
|
+
echo -e " View logs: ${YELLOW}${COMPOSE_CMD} logs -f superbrain-api${NC}"
|
|
121
|
+
echo -e " Stop services: ${YELLOW}${COMPOSE_CMD} down${NC}"
|
|
122
|
+
echo -e " Restart: ${YELLOW}${COMPOSE_CMD} restart${NC}"
|
|
123
|
+
echo -e " Status: ${YELLOW}${COMPOSE_CMD} ps${NC}"
|
|
124
|
+
|
|
125
|
+
echo -e "\n"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# SuperBrain Docker Setup Script
|
|
4
|
+
# One-click script to build and start the SuperBrain backend
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
echo "🐳 SuperBrain Docker Setup"
|
|
9
|
+
echo "=========================="
|
|
10
|
+
|
|
11
|
+
# Check if Docker is installed
|
|
12
|
+
if ! command -v docker &> /dev/null; then
|
|
13
|
+
echo "❌ Docker is not installed. Please install Docker first."
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# Check if docker-compose or docker compose is available
|
|
18
|
+
if ! command -v docker compose &> /dev/null && ! command -v docker-compose &> /dev/null; then
|
|
19
|
+
echo "❌ Docker Compose is not installed. Please install Docker Compose first."
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Use docker compose if available, otherwise docker-compose
|
|
24
|
+
if command -v docker compose &> /dev/null; then
|
|
25
|
+
DOCKER_COMPOSE="docker compose"
|
|
26
|
+
else
|
|
27
|
+
DOCKER_COMPOSE="docker-compose"
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Check if .env exists, if not create from example
|
|
31
|
+
if [ ! -f .env ]; then
|
|
32
|
+
echo "📝 Creating .env file from example..."
|
|
33
|
+
if [ -f .env.example ]; then
|
|
34
|
+
cp .env.example .env
|
|
35
|
+
echo "✅ Created .env file. Please edit it with your API keys."
|
|
36
|
+
else
|
|
37
|
+
echo "⚠️ No .env.example found, continuing without environment variables."
|
|
38
|
+
fi
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Build and start the container
|
|
42
|
+
echo "🔨 Building Docker image..."
|
|
43
|
+
$DOCKER_COMPOSE build
|
|
44
|
+
|
|
45
|
+
echo "🚀 Starting SuperBrain..."
|
|
46
|
+
$DOCKER_COMPOSE up -d
|
|
47
|
+
|
|
48
|
+
echo ""
|
|
49
|
+
echo "✅ SuperBrain is now running!"
|
|
50
|
+
echo "📖 API: http://localhost:5000"
|
|
51
|
+
echo "📚 Docs: http://localhost:5000/docs"
|
|
52
|
+
echo ""
|
|
53
|
+
echo "Useful commands:"
|
|
54
|
+
echo " $DOCKER_COMPOSE logs -f # View logs"
|
|
55
|
+
echo " $DOCKER_COMPOSE down # Stop the server"
|
|
56
|
+
echo " $DOCKER_COMPOSE restart # Restart the server"
|