Skip to content

Commit 449e736

Browse files
feat(docker): enhance Dockerfile and installation script for Obsidian plugin integration
- Added a multi-stage build to the Dockerfile to compile the Obsidian plugin, reducing user setup complexity. - Updated the installation script to persist the OBSIDIAN_VAULT_PATH in the user's shell profile for easier access. - Improved the completion screen to prioritize pre-built plugin files from the Docker image, enhancing installation reliability. - Refactored the vault selection screen to pre-populate the vault path from the environment variable, improving user experience.
1 parent f31b09b commit 449e736

File tree

5 files changed

+203
-160
lines changed

5 files changed

+203
-160
lines changed

Dockerfile.setup

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Dockerfile for Thoth setup wizard (config only — no Docker-in-Docker)
22

3+
# --- Stage 1: Build the Obsidian plugin so users don't need Node/npm ---
4+
FROM node:20-slim AS plugin-builder
5+
6+
WORKDIR /plugin
7+
COPY obsidian-plugin/thoth-obsidian/ ./
8+
RUN npm ci --ignore-scripts && npm run build
9+
10+
# --- Stage 2: Setup wizard image ---
311
FROM python:3.12-slim
412

513
WORKDIR /app
@@ -17,6 +25,11 @@ COPY src/ ./src/
1725
COPY templates/ ./templates/
1826
COPY data/ ./data/
1927

28+
# Copy pre-built plugin artifacts from the Node build stage
29+
COPY --from=plugin-builder /plugin/dist/ ./obsidian-plugin/thoth-obsidian/dist/
30+
COPY obsidian-plugin/thoth-obsidian/manifest.json ./obsidian-plugin/thoth-obsidian/manifest.json
31+
COPY obsidian-plugin/thoth-obsidian/styles.css ./obsidian-plugin/thoth-obsidian/styles.css
32+
2033
# Install Thoth with setup dependencies only
2134
RUN pip install --no-cache-dir -e ".[setup]"
2235

docker/letta/init-schema.sh

Lines changed: 58 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,70 @@
11
#!/bin/bash
22
set -e
33

4-
echo "==> Initializing Letta database schema..."
4+
# Start Letta server in the background. Letta handles its own schema
5+
# creation and seeds the default org/user on first boot — we don't need
6+
# to create tables or insert rows manually.
7+
echo "==> Starting Letta server..."
8+
letta server --host 0.0.0.0 --port 8283 --ade &
9+
LETTA_PID=$!
510

6-
# Wait for PostgreSQL to be ready
7-
until python3 -c "import psycopg2; psycopg2.connect('$LETTA_PG_URI')" 2>/dev/null; do
8-
echo "Waiting for PostgreSQL..."
9-
sleep 2
11+
# Wait for Letta to become healthy (it creates the schema + default org
12+
# during startup, so the /v1/health endpoint only succeeds after that).
13+
echo "==> Waiting for Letta to be ready..."
14+
MAX_WAIT=180
15+
WAITED=0
16+
until curl -sf http://localhost:8283/v1/health > /dev/null 2>&1; do
17+
if ! kill -0 "$LETTA_PID" 2>/dev/null; then
18+
echo "==> ERROR: Letta process exited unexpectedly"
19+
exit 1
20+
fi
21+
if [ "$WAITED" -ge "$MAX_WAIT" ]; then
22+
echo "==> ERROR: Letta did not become healthy within ${MAX_WAIT}s"
23+
exit 1
24+
fi
25+
sleep 3
26+
WAITED=$((WAITED + 3))
27+
echo " Still waiting... (${WAITED}s)"
1028
done
1129

12-
echo "PostgreSQL is ready!"
13-
14-
# Create schema using Letta ORM
15-
python3 << 'EOF'
16-
import os
17-
from letta.orm import Base
18-
from sqlalchemy import create_engine, inspect
19-
20-
# Get connection string from environment
21-
pg_uri = os.environ.get('LETTA_PG_URI')
22-
print(f"Creating schema with URI: {pg_uri}")
30+
echo "==> Letta is healthy (took ~${WAITED}s)"
2331

24-
# Create engine and tables
25-
engine = create_engine(pg_uri, echo=True)
26-
Base.metadata.create_all(engine)
32+
# Register the Thoth MCP server via the REST API. This avoids the
33+
# foreign-key issues that come from inserting directly into the DB
34+
# before Letta seeds its default organization.
35+
# THOTH_MCP_URL comes from docker-compose.letta.yml (default: http://thoth-mcp:8001).
36+
# The MCP endpoint path is /mcp under that base URL.
37+
THOTH_MCP_BASE="${THOTH_MCP_URL:-http://thoth-mcp:8001}"
38+
THOTH_MCP_ENDPOINT="${THOTH_MCP_BASE}/mcp"
2739

28-
# Verify tables were created
29-
inspector = inspect(engine)
30-
tables = inspector.get_table_names()
31-
print(f"\n==> Created {len(tables)} tables:")
32-
for table in sorted(tables):
33-
print(f" ✓ {table}")
40+
echo "==> Registering Thoth MCP server via API..."
3441

35-
if 'organizations' in tables:
36-
print("\n==> Schema initialization successful!")
37-
else:
38-
print("\n==> ERROR: organizations table not found!")
39-
exit(1)
40-
EOF
42+
# Check if already registered
43+
EXISTING=$(curl -sf http://localhost:8283/v1/tools/mcp/servers 2>/dev/null || echo "[]")
44+
if echo "$EXISTING" | grep -q '"thoth-research-tools"'; then
45+
echo "==> Thoth MCP server already registered"
46+
else
47+
# The /v1/tools/mcp/servers endpoint registers a new MCP server.
48+
# Letta 0.16+ uses streamable_http transport.
49+
RESPONSE=$(curl -sf -X POST http://localhost:8283/v1/tools/mcp/servers \
50+
-H "Content-Type: application/json" \
51+
-d "{
52+
\"server_name\": \"thoth-research-tools\",
53+
\"server_type\": \"streamable_http\",
54+
\"server_url\": \"${THOTH_MCP_ENDPOINT}\"
55+
}" 2>&1) || true
4156

42-
# Register Thoth MCP server in database
43-
echo "==> Registering Thoth MCP server in database..."
44-
python3 << 'EOF'
45-
import os
46-
import uuid
47-
from sqlalchemy import create_engine, text
57+
if echo "$RESPONSE" | grep -q '"id"'; then
58+
echo "==> Thoth MCP server registered successfully"
59+
else
60+
# Non-fatal: the MCP server can be registered later via the UI
61+
echo "==> Warning: Could not register MCP server via API."
62+
echo " Response: ${RESPONSE}"
63+
echo " You can register it manually in the Letta ADE."
64+
fi
65+
fi
4866

49-
pg_uri = os.environ.get('LETTA_PG_URI')
50-
engine = create_engine(pg_uri)
67+
echo "==> Letta initialization complete, server running (pid $LETTA_PID)"
5168

52-
# Check if MCP server already registered
53-
with engine.connect() as conn:
54-
result = conn.execute(text("SELECT COUNT(*) FROM mcp_server WHERE server_name = 'thoth-research-tools'"))
55-
count = result.scalar()
56-
57-
if count == 0:
58-
# Insert MCP server registration
59-
# Note: Letta requires 'mcp_server-' prefix (underscore, not hyphen)
60-
mcp_server_id = f"mcp_server-{uuid.uuid4()}"
61-
conn.execute(text("""
62-
INSERT INTO mcp_server (
63-
id, server_name, server_type, server_url,
64-
organization_id, is_deleted,
65-
_created_by_id, _last_updated_by_id
66-
) VALUES (
67-
:id, :name, :type, :url,
68-
'org-00000000-0000-4000-8000-000000000000', false,
69-
'user-00000000-0000-4000-8000-000000000000',
70-
'user-00000000-0000-4000-8000-000000000000'
71-
)
72-
"""), {
73-
"id": mcp_server_id,
74-
"name": "thoth-research-tools",
75-
"type": "streamable_http",
76-
"url": "http://thoth-mcp:8000/mcp"
77-
})
78-
conn.commit()
79-
print(f"✓ Registered Thoth MCP server: {mcp_server_id}")
80-
else:
81-
print("✓ Thoth MCP server already registered")
82-
EOF
83-
84-
echo "==> Starting Letta server..."
85-
exec letta server --host 0.0.0.0 --port 8283 --ade
69+
# Bring the Letta process to the foreground so Docker can track it
70+
wait "$LETTA_PID"

install.sh

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,12 @@ export THOTH_IMAGE_TAG="${THOTH_IMAGE_TAG:-latest}"
111111
112112
case "$1" in
113113
start)
114-
echo "🚀 Starting Thoth services..."
114+
echo "Starting Thoth services..."
115115
echo " Image tag: ${THOTH_IMAGE_TAG}"
116116
cd "$PROJECT_ROOT"
117117
118118
if [ ! -f ".env" ] || ! grep -q 'OBSIDIAN_VAULT_PATH' .env 2>/dev/null; then
119-
echo " .env file missing or OBSIDIAN_VAULT_PATH not set."
119+
echo "Error: .env file missing or OBSIDIAN_VAULT_PATH not set."
120120
echo " Run the setup wizard first, or create .env from .env.example:"
121121
echo " cp .env.example .env && edit .env"
122122
exit 1
@@ -142,7 +142,7 @@ case "$1" in
142142
echo " Starting Thoth containers..."
143143
docker compose -f docker-compose.dev.yml --profile microservices up -d
144144
145-
echo "Thoth is running!"
145+
echo "Thoth is running!"
146146
[ "$LETTA_MODE" = "cloud" ] && echo " Letta: Cloud" || echo " Letta: localhost:8283"
147147
echo " API: http://localhost:8000"
148148
echo " MCP: http://localhost:8082"
@@ -560,6 +560,33 @@ echo -e "${YELLOW}Installing via Docker...${NC}\n"
560560
touch "$PROJECT_ROOT/.env.letta"
561561
fi
562562

563+
# Persist OBSIDIAN_VAULT_PATH into the user's shell profile so it's
564+
# always available (Docker Compose volume mounts read it at runtime).
565+
if [ -f "$PROJECT_ROOT/.env" ]; then
566+
VAULT_PATH=$(grep '^OBSIDIAN_VAULT_PATH=' "$PROJECT_ROOT/.env" 2>/dev/null | cut -d'=' -f2-)
567+
if [ -n "$VAULT_PATH" ]; then
568+
EXPORT_LINE="export OBSIDIAN_VAULT_PATH=\"$VAULT_PATH\""
569+
570+
for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do
571+
if [ -f "$rc" ]; then
572+
# Remove any stale export, then append the current one
573+
grep -v 'export OBSIDIAN_VAULT_PATH=' "$rc" > "${rc}.thoth_tmp" || true
574+
echo "$EXPORT_LINE" >> "${rc}.thoth_tmp"
575+
mv "${rc}.thoth_tmp" "$rc"
576+
fi
577+
done
578+
579+
# On macOS, create .zshrc if it doesn't exist (default shell is zsh)
580+
if [ "$(uname)" = "Darwin" ] && [ ! -f "$HOME/.zshrc" ]; then
581+
echo "$EXPORT_LINE" > "$HOME/.zshrc"
582+
fi
583+
584+
# Export for the remainder of this script
585+
export OBSIDIAN_VAULT_PATH="$VAULT_PATH"
586+
echo -e "${GREEN}✓ OBSIDIAN_VAULT_PATH persisted to shell profile${NC}"
587+
fi
588+
fi
589+
563590
# Offer to start services now
564591
echo -e "${BLUE}Would you like to start Thoth services now? (y/n)${NC}"
565592
if [ -t 0 ]; then

0 commit comments

Comments
 (0)