-
Notifications
You must be signed in to change notification settings - Fork 228
Expand file tree
/
Copy pathstart_services.py
More file actions
executable file
·608 lines (515 loc) · 25.4 KB
/
Copy pathstart_services.py
File metadata and controls
executable file
·608 lines (515 loc) · 25.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
#!/usr/bin/env python3
"""
start_services.py
This script starts the Supabase stack first, waits for it to initialize, and then starts
the local AI stack. Both stacks use the same Docker Compose project name ("localai")
so they appear together in Docker Desktop.
"""
import os
import re
import subprocess
import shutil
import time
import platform
import yaml
from dotenv import dotenv_values
def is_supabase_enabled():
"""Check if 'supabase' is in COMPOSE_PROFILES in .env file."""
env_values = dotenv_values(".env")
compose_profiles = env_values.get("COMPOSE_PROFILES", "")
return "supabase" in compose_profiles.split(',')
def is_dify_enabled():
"""Check if 'dify' is in COMPOSE_PROFILES in .env file."""
env_values = dotenv_values(".env")
compose_profiles = env_values.get("COMPOSE_PROFILES", "")
return "dify" in compose_profiles.split(',')
def get_all_profiles(compose_file):
"""Get all profile names from a docker-compose file."""
if not os.path.exists(compose_file):
return []
with open(compose_file, 'r') as f:
compose_config = yaml.safe_load(f)
profiles = set()
if 'services' in compose_config:
for service_name, service_config in compose_config.get('services', {}).items():
if service_config and 'profiles' in service_config:
for profile in service_config['profiles']:
profiles.add(profile)
return list(profiles)
def run_command(cmd, cwd=None):
"""Run a shell command and print it."""
print("Running:", " ".join(cmd))
subprocess.run(cmd, cwd=cwd, check=True)
def clone_supabase_repo():
"""Clone the Supabase repository using sparse checkout if not already present."""
if not is_supabase_enabled():
print("Supabase is not enabled, skipping clone.")
return
if not os.path.exists("supabase"):
print("Cloning the Supabase repository...")
run_command([
"git", "clone", "--filter=blob:none", "--no-checkout",
"https://github.com/supabase/supabase.git"
])
os.chdir("supabase")
run_command(["git", "sparse-checkout", "init", "--cone"])
run_command(["git", "sparse-checkout", "set", "docker"])
run_command(["git", "checkout", "master"])
os.chdir("..")
else:
print("Supabase repository already exists, updating...")
os.chdir("supabase")
# Discard any local postgres image pin so 'git pull' stays conflict-free;
# pin_supabase_postgres_image() re-applies it below after the pull.
subprocess.run(["git", "checkout", "HEAD", "--", "docker/docker-compose.yml"], check=False)
run_command(["git", "pull"])
os.chdir("..")
# Keep supabase-db on the Postgres major version matching existing data (issue #64).
pin_supabase_postgres_image()
# Pins for supabase/postgres keyed by the data's major version. When Supabase
# upstream bumps the postgres image to a new MAJOR version, existing data volumes
# on an older major can no longer be read. Map the on-disk major to a known-good
# image tag so 'make update' keeps existing databases working instead of silently
# upgrading them into an unhealthy state.
SUPABASE_POSTGRES_PINS = {
"15": "supabase/postgres:15.8.1.085",
}
def pin_supabase_postgres_image():
"""Keep supabase-db on the Postgres major version matching existing data.
Supabase upstream periodically bumps supabase/postgres to a new major version
(e.g. 15 -> 17). PostgreSQL's on-disk format is not compatible across majors,
so a silent bump leaves supabase-db unhealthy on an existing data volume.
This reads the major version of the data already on disk and, if upstream now
wants a different major, pins the image in the freshly pulled compose file back
to a compatible tag. Fresh installs (no data yet) are left on upstream.
"""
compose_path = os.path.join("supabase", "docker", "docker-compose.yml")
pg_version_path = os.path.join("supabase", "docker", "volumes", "db", "data", "PG_VERSION")
# No existing data (fresh install) or no compose yet: use upstream as-is.
if not (os.path.exists(compose_path) and os.path.exists(pg_version_path)):
return
with open(pg_version_path) as f:
data_major = f.read().strip()
with open(compose_path) as f:
content = f.read()
match = re.search(r"image:\s*supabase/postgres:(\S+)", content)
if not match:
# Data exists (checked above) but the image line is missing — e.g. an upstream
# compose refactor. Don't silently skip the safety pin; warn so the likely
# supabase-db breakage is traceable instead of a mysterious unhealthy container.
print(
f"WARNING: Existing Supabase data is on PG{data_major}, but the "
f"supabase/postgres image line was not found in {compose_path}. The version "
f"pin could not be applied; supabase-db may start on an incompatible major."
)
return
upstream_tag = match.group(1)
upstream_major = upstream_tag.split(".")[0]
# Same major as the data on disk: allow patch-level updates, nothing to pin.
if upstream_major == data_major:
return
pin = SUPABASE_POSTGRES_PINS.get(data_major)
if not pin:
print(
f"WARNING: Supabase upstream now uses postgres major {upstream_major}, "
f"but your data volume is on major {data_major} and no compatible pin is "
f"known. supabase-db may fail to start; a manual data migration is required."
)
return
# The regex requires a ':' immediately after "supabase/postgres", so it matches
# only the db service's image, not "supabase/postgres-meta" (the meta service).
# count=1 is belt-and-suspenders against any future second match.
new_content = content.replace(match.group(0), f"image: {pin}", 1)
with open(compose_path, "w") as f:
f.write(new_content)
print(
f"Pinned Supabase Postgres to {pin} to match your existing PG{data_major} data "
f"(upstream moved to {upstream_tag}). 'make update' will not upgrade the major "
f"version automatically; a manual migration is required to move to PG{upstream_major}."
)
def prepare_supabase_env():
"""Copy .env to supabase/docker/.env, or sync new variables if it already exists."""
if not is_supabase_enabled():
print("Supabase is not enabled, skipping env preparation.")
return
env_path = os.path.join("supabase", "docker", ".env")
root_env_path = ".env"
if os.path.exists(env_path):
# Sync new variables from root .env that don't exist in supabase .env
print(f"Syncing new variables from root .env to {env_path}...")
root_env = dotenv_values(root_env_path)
supabase_env = dotenv_values(env_path)
new_vars = []
for key, value in root_env.items():
if key not in supabase_env and value is not None:
# Quote values to handle special characters safely
if '$' in value:
new_vars.append(f"{key}='{value}'")
else:
new_vars.append(f'{key}="{value}"')
if new_vars:
with open(env_path, 'r') as f:
existing_content = f.read()
sync_header = "# --- Variables synced from root .env ---"
with open(env_path, 'a') as f:
if sync_header not in existing_content:
f.write(f"\n{sync_header}\n")
for var in new_vars:
f.write(f"{var}\n")
print(f"Synced {len(new_vars)} new variable(s) to Supabase env.")
else:
print("Supabase env is up to date, no new variables to sync.")
return
print("Copying .env in root to .env in supabase/docker...")
shutil.copyfile(root_env_path, env_path)
def clone_dify_repo():
"""Clone the Dify repository using sparse checkout if not already present."""
if not is_dify_enabled():
print("Dify is not enabled, skipping clone.")
return
if not os.path.exists("dify"):
print("Cloning the Dify repository...")
run_command([
"git", "clone", "--filter=blob:none", "--no-checkout",
"https://github.com/langgenius/dify.git"
])
os.chdir("dify")
run_command(["git", "sparse-checkout", "init", "--cone"])
run_command(["git", "sparse-checkout", "set", "docker"])
# Dify's default branch is 'main'
run_command(["git", "checkout", "main"])
os.chdir("..")
else:
print("Dify repository already exists, updating...")
os.chdir("dify")
run_command(["git", "pull"])
os.chdir("..")
def prepare_dify_env():
"""Create dify/docker/.env from env.example and inject selected values from root .env.
Mapping (strip DIFY_ prefix from root .env):
- DIFY_SECRET_KEY -> SECRET_KEY
- DIFY_EXPOSE_NGINX_PORT -> EXPOSE_NGINX_PORT
- DIFY_EXPOSE_NGINX_SSL_PORT -> EXPOSE_NGINX_SSL_PORT
"""
if not is_dify_enabled():
print("Dify is not enabled, skipping env preparation.")
return
dify_docker_dir = os.path.join("dify", "docker")
if not os.path.isdir(dify_docker_dir):
print(f"Warning: Dify docker directory not found at {dify_docker_dir}. Have you cloned the repo?")
return
# Determine env example file name: prefer 'env.example', fallback to '.env.example'
env_example_candidates = [
os.path.join(dify_docker_dir, "env.example"),
os.path.join(dify_docker_dir, ".env.example"),
]
env_example_path = next((p for p in env_example_candidates if os.path.exists(p)), None)
if env_example_path is None:
print(f"Warning: Could not find env.example in {dify_docker_dir}")
return
env_path = os.path.join(dify_docker_dir, ".env")
print(f"Creating {env_path} from {env_example_path}...")
with open(env_example_path, 'r') as f:
env_content = f.read()
# Load values from root .env
root_env = dotenv_values(".env")
mapping = {
"SECRET_KEY": root_env.get("DIFY_SECRET_KEY", ""),
"EXPOSE_NGINX_PORT": root_env.get("DIFY_EXPOSE_NGINX_PORT", ""),
"EXPOSE_NGINX_SSL_PORT": root_env.get("DIFY_EXPOSE_NGINX_SSL_PORT", ""),
}
# Replace or append variables in env_content
lines = env_content.splitlines()
replaced_keys = set()
for i, line in enumerate(lines):
for dest_key, value in mapping.items():
if line.startswith(f"{dest_key}=") and value:
lines[i] = f"{dest_key}={value}"
replaced_keys.add(dest_key)
# Append any missing keys with values
for dest_key, value in mapping.items():
if value and dest_key not in replaced_keys:
lines.append(f"{dest_key}={value}")
with open(env_path, 'w') as f:
f.write("\n".join(lines) + "\n")
def is_postiz_enabled():
"""Check if 'postiz' is in COMPOSE_PROFILES in .env file."""
env_values = dotenv_values(".env")
compose_profiles = env_values.get("COMPOSE_PROFILES", "")
return "postiz" in compose_profiles.split(',')
def prepare_postiz_env():
"""Generate postiz.env for mounting as /app/.env in Postiz container.
The Postiz image uses dotenv-cli (dotenv -e ../../.env) to load config.
Always regenerated to reflect current .env values.
"""
if not is_postiz_enabled():
print("Postiz is not enabled, skipping env preparation.")
return
print("Generating postiz.env from root .env values...")
root_env = dotenv_values(".env")
hostname = root_env.get("POSTIZ_HOSTNAME", "")
frontend_url = f"https://{hostname}" if hostname else ""
env_vars = {
"BACKEND_INTERNAL_URL": "http://localhost:3000",
"DATABASE_URL": f"postgresql://postgres:{root_env.get('POSTGRES_PASSWORD', '')}@postgres:5432/{root_env.get('POSTIZ_DB_NAME', 'postiz')}?schema=postiz",
"DISABLE_REGISTRATION": root_env.get("POSTIZ_DISABLE_REGISTRATION", "false"),
"FRONTEND_URL": frontend_url,
"IS_GENERAL": "true",
"JWT_SECRET": root_env.get("JWT_SECRET", ""),
"MAIN_URL": frontend_url,
"NEXT_PUBLIC_BACKEND_URL": f"{frontend_url}/api" if frontend_url else "",
"NEXT_PUBLIC_UPLOAD_DIRECTORY": "/uploads",
"REDIS_URL": "redis://redis:6379",
"STORAGE_PROVIDER": "local",
"TEMPORAL_ADDRESS": "temporal:7233",
"UPLOAD_DIRECTORY": "/uploads",
}
# Social media API keys — direct pass-through from root .env
social_keys = [
"X_API_KEY", "X_API_SECRET",
"LINKEDIN_CLIENT_ID", "LINKEDIN_CLIENT_SECRET",
"REDDIT_CLIENT_ID", "REDDIT_CLIENT_SECRET",
"GITHUB_CLIENT_ID", "GITHUB_CLIENT_SECRET",
"BEEHIIVE_API_KEY", "BEEHIIVE_PUBLICATION_ID",
"THREADS_APP_ID", "THREADS_APP_SECRET",
"FACEBOOK_APP_ID", "FACEBOOK_APP_SECRET",
"YOUTUBE_CLIENT_ID", "YOUTUBE_CLIENT_SECRET",
"TIKTOK_CLIENT_ID", "TIKTOK_CLIENT_SECRET",
"PINTEREST_CLIENT_ID", "PINTEREST_CLIENT_SECRET",
"DRIBBBLE_CLIENT_ID", "DRIBBBLE_CLIENT_SECRET",
"DISCORD_CLIENT_ID", "DISCORD_CLIENT_SECRET",
"DISCORD_BOT_TOKEN_ID",
"SLACK_ID", "SLACK_SECRET", "SLACK_SIGNING_SECRET",
"MASTODON_URL", "MASTODON_CLIENT_ID", "MASTODON_CLIENT_SECRET",
]
for key in social_keys:
env_vars[key] = root_env.get(key, "")
# Handle case where Docker created postiz.env as a directory
if os.path.isdir("postiz.env"):
print("Warning: postiz.env exists as a directory (likely created by Docker). Removing...")
shutil.rmtree("postiz.env")
with open("postiz.env", 'w') as f:
for key, value in env_vars.items():
f.write(f'{key}="{value}"\n')
print(f"Generated postiz.env with {len(env_vars)} variables.")
def stop_existing_containers():
"""Stop and remove existing containers for our unified project ('localai')."""
print("Stopping and removing existing containers for the unified project 'localai'...")
# Base command with project name for consistency
cmd = ["docker", "compose", "-p", "localai"]
# Get all profiles from the main docker-compose.yml to ensure all services can be brought down
all_profiles = get_all_profiles("docker-compose.yml")
for profile in all_profiles:
cmd.extend(["--profile", profile])
cmd.extend(["-f", "docker-compose.yml"])
# Check if the Supabase Docker Compose file exists. If so, include it in the 'down' command.
supabase_compose_path = os.path.join("supabase", "docker", "docker-compose.yml")
if os.path.exists(supabase_compose_path):
cmd.extend(["-f", supabase_compose_path])
# Check if the Dify Docker Compose file exists. If so, include it in the 'down' command.
dify_compose_path = os.path.join("dify", "docker", "docker-compose.yaml")
if os.path.exists(dify_compose_path):
cmd.extend(["-f", dify_compose_path])
for profile in get_all_profiles(dify_compose_path):
cmd.extend(["--profile", profile])
# Check if the n8n workers compose file exists. If so, include it in the 'down' command.
n8n_workers_compose_path = "docker-compose.n8n-workers.yml"
if os.path.exists(n8n_workers_compose_path):
cmd.extend(["-f", n8n_workers_compose_path])
# Include user overrides if present
override_path = "docker-compose.override.yml"
if os.path.exists(override_path):
cmd.extend(["-f", override_path])
cmd.extend(["down"])
run_command(cmd)
def start_supabase():
"""Start the Supabase services (using its compose file)."""
if not is_supabase_enabled():
print("Supabase is not enabled, skipping start.")
return
print("Starting Supabase services...")
run_command([
"docker", "compose", "-p", "localai", "-f", "supabase/docker/docker-compose.yml", "up", "-d"
])
def start_dify():
"""Start the Dify services (using its compose file)."""
if not is_dify_enabled():
print("Dify is not enabled, skipping start.")
return
print("Starting Dify services...")
run_command([
"docker", "compose", "-p", "localai",
"--profile", "postgresql",
"--profile", "weaviate",
"-f", "dify/docker/docker-compose.yaml",
"up", "-d",
])
def start_local_ai():
"""Start the local AI services (using its compose file)."""
print("Starting local AI services...")
# Build compose files list
compose_files = ["-f", "docker-compose.yml"]
# Check if n8n workers compose file exists (generated by generate_n8n_workers.sh)
n8n_workers_compose_path = "docker-compose.n8n-workers.yml"
if os.path.exists(n8n_workers_compose_path):
compose_files.extend(["-f", n8n_workers_compose_path])
# Include user overrides if present (must be last for highest precedence)
override_path = "docker-compose.override.yml"
if os.path.exists(override_path):
compose_files.extend(["-f", override_path])
# Explicitly build services and pull newer base images first.
print("Checking for newer base images and building services...")
build_cmd = ["docker", "compose", "-p", "localai"] + compose_files + ["build", "--pull"]
run_command(build_cmd)
# Now, start the services using the newly built images. No --build needed as we just built.
print("Starting containers...")
up_cmd = ["docker", "compose", "-p", "localai"] + compose_files + ["up", "-d"]
run_command(up_cmd)
def generate_searxng_secret_key():
"""Generate a secret key for SearXNG based on the current platform."""
print("Checking SearXNG settings...")
# Define paths for SearXNG settings files
settings_path = os.path.join("searxng", "settings.yml")
settings_base_path = os.path.join("searxng", "settings-base.yml")
# Check if settings-base.yml exists
if not os.path.exists(settings_base_path):
print(f"Warning: SearXNG base settings file not found at {settings_base_path}")
return
# Check if settings.yml exists, if not create it from settings-base.yml
if not os.path.exists(settings_path):
print(f"SearXNG settings.yml not found. Creating from {settings_base_path}...")
try:
shutil.copyfile(settings_base_path, settings_path)
print(f"Created {settings_path} from {settings_base_path}")
except Exception as e:
print(f"Error creating settings.yml: {e}")
return
else:
print(f"SearXNG settings.yml already exists at {settings_path}")
print("Generating SearXNG secret key...")
# Detect the platform and run the appropriate command
system = platform.system()
try:
if system == "Windows":
print("Detected Windows platform, using PowerShell to generate secret key...")
# PowerShell command to generate a random key and replace in the settings file
ps_command = [
"powershell", "-Command",
"$randomBytes = New-Object byte[] 32; " +
"(New-Object Security.Cryptography.RNGCryptoServiceProvider).GetBytes($randomBytes); " +
"$secretKey = -join ($randomBytes | ForEach-Object { \"{0:x2}\" -f $_ }); " +
"(Get-Content searxng/settings.yml) -replace 'ultrasecretkey', $secretKey | Set-Content searxng/settings.yml"
]
subprocess.run(ps_command, check=True)
elif system == "Darwin": # macOS
print("Detected macOS platform, using sed command with empty string parameter...")
# macOS sed command requires an empty string for the -i parameter
openssl_cmd = ["openssl", "rand", "-hex", "32"]
random_key = subprocess.check_output(openssl_cmd).decode('utf-8').strip()
sed_cmd = ["sed", "-i", "", f"s|ultrasecretkey|{random_key}|g", settings_path]
subprocess.run(sed_cmd, check=True)
else: # Linux and other Unix-like systems
print("Detected Linux/Unix platform, using standard sed command...")
# Standard sed command for Linux
openssl_cmd = ["openssl", "rand", "-hex", "32"]
random_key = subprocess.check_output(openssl_cmd).decode('utf-8').strip()
sed_cmd = ["sed", "-i", f"s|ultrasecretkey|{random_key}|g", settings_path]
subprocess.run(sed_cmd, check=True)
print("SearXNG secret key generated successfully.")
except Exception as e:
print(f"Error generating SearXNG secret key: {e}")
print("You may need to manually generate the secret key using the commands:")
print(" - Linux: sed -i \"s|ultrasecretkey|$(openssl rand -hex 32)|g\" searxng/settings.yml")
print(" - macOS: sed -i '' \"s|ultrasecretkey|$(openssl rand -hex 32)|g\" searxng/settings.yml")
print(" - Windows (PowerShell):")
print(" $randomBytes = New-Object byte[] 32")
print(" (New-Object Security.Cryptography.RNGCryptoServiceProvider).GetBytes($randomBytes)")
print(" $secretKey = -join ($randomBytes | ForEach-Object { \"{0:x2}\" -f $_ })")
print(" (Get-Content searxng/settings.yml) -replace 'ultrasecretkey', $secretKey | Set-Content searxng/settings.yml")
def check_and_fix_docker_compose_for_searxng():
"""Check and modify docker-compose.yml for SearXNG first run."""
docker_compose_path = "docker-compose.yml"
if not os.path.exists(docker_compose_path):
print(f"Warning: Docker Compose file not found at {docker_compose_path}")
return
try:
# Read the docker-compose.yml file
with open(docker_compose_path, 'r') as file:
content = file.read()
# Default to first run
is_first_run = True
# Check if Docker is running and if the SearXNG container exists
try:
# Check if the SearXNG container is running
container_check = subprocess.run(
["docker", "ps", "--filter", "name=searxng", "--format", "{{.Names}}"],
capture_output=True, text=True, check=True
)
searxng_containers = container_check.stdout.strip().split('\n')
# If SearXNG container is running, check inside for uwsgi.ini
if any(container for container in searxng_containers if container):
container_name = next(container for container in searxng_containers if container)
print(f"Found running SearXNG container: {container_name}")
# Check if uwsgi.ini exists inside the container
container_check = subprocess.run(
["docker", "exec", container_name, "sh", "-c", "[ -f /etc/searxng/uwsgi.ini ] && echo 'found' || echo 'not_found'"],
capture_output=True, text=True, check=False
)
if "found" in container_check.stdout:
print("Found uwsgi.ini inside the SearXNG container - not first run")
is_first_run = False
else:
print("uwsgi.ini not found inside the SearXNG container - first run")
is_first_run = True
else:
print("No running SearXNG container found - assuming first run")
except Exception as e:
print(f"Error checking Docker container: {e} - assuming first run")
if is_first_run and "cap_drop: - ALL" in content:
print("First run detected for SearXNG. Temporarily removing 'cap_drop: - ALL' directive...")
# Temporarily comment out the cap_drop line
modified_content = content.replace("cap_drop: - ALL", "# cap_drop: - ALL # Temporarily commented out for first run")
# Write the modified content back
with open(docker_compose_path, 'w') as file:
file.write(modified_content)
print("Note: After the first run completes successfully, you should re-add 'cap_drop: - ALL' to docker-compose.yml for security reasons.")
elif not is_first_run and "# cap_drop: - ALL # Temporarily commented out for first run" in content:
print("SearXNG has been initialized. Re-enabling 'cap_drop: - ALL' directive for security...")
# Uncomment the cap_drop line and ensure correct multi-line YAML format
correct_cap_drop_block = "cap_drop:\n - ALL" # Note the newline and indentation for the list item
modified_content = content.replace("# cap_drop: - ALL # Temporarily commented out for first run", correct_cap_drop_block)
# Write the modified content back
with open(docker_compose_path, 'w') as file:
file.write(modified_content)
except Exception as e:
print(f"Error checking/modifying docker-compose.yml for SearXNG: {e}")
def main():
# Clone and prepare repositories
if is_supabase_enabled():
clone_supabase_repo()
prepare_supabase_env()
if is_dify_enabled():
clone_dify_repo()
prepare_dify_env()
# Generate SearXNG secret key and check docker-compose.yml
generate_searxng_secret_key()
check_and_fix_docker_compose_for_searxng()
# Generate Postiz env file
prepare_postiz_env()
stop_existing_containers()
# Start Supabase first
if is_supabase_enabled():
start_supabase()
# Give Supabase some time to initialize
print("Waiting for Supabase to initialize...")
time.sleep(10)
# Start Dify services
if is_dify_enabled():
start_dify()
# Give Dify some time to initialize
print("Waiting for Dify to initialize...")
time.sleep(10)
# Then start the local AI services
start_local_ai()
if __name__ == "__main__":
main()