Skip to content

Commit bb1678f

Browse files
Lexecon Devclaude
andcommitted
fix: run migrations on startup and respect LEXECON_DATA_DIR for all DB paths
- Add start.sh: runs migrations/run_all.py then exec uvicorn (no server starts without migrations completing first; exits non-zero on failure) - Dockerfile: copy start.sh, use it as CMD, extend healthcheck start-period to 60s to allow migration time, fix /app chown for non-root user - railway.toml: update startCommand to /bin/sh /app/start.sh - dependencies.py: replace all hardcoded DB filenames with paths resolved under LEXECON_DATA_DIR (defaults to "." for local dev). Covers: lexecon_ledger.db, lexecon_auth.db, lexecon_responsibility.db, lexecon_export_audit.db, lexecon_keys/, lexecon_interventions.db, lexecon_usage.db, lexecon_auth.db (TenancyService) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 3ece716 commit bb1678f

4 files changed

Lines changed: 43 additions & 14 deletions

File tree

Dockerfile

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,12 @@ COPY --from=python-builder /build/pyproject.toml .
5252
# Copy React build output
5353
COPY --from=frontend-builder /frontend/build ./static
5454

55-
# Copy migrations
55+
# Copy migrations and startup script
5656
COPY migrations/ ./migrations/
57+
COPY start.sh ./start.sh
5758

5859
# Create data directory
59-
RUN mkdir -p /data/.lexecon && chown -R lexecon:lexecon /data
60+
RUN mkdir -p /data/.lexecon && chown -R lexecon:lexecon /data /app
6061

6162
# Set environment variables
6263
ENV PATH=/home/lexecon/.local/bin:$PATH \
@@ -72,11 +73,11 @@ USER lexecon
7273
EXPOSE 8000
7374

7475
# Health check using curl instead of python+requests
75-
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
76+
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
7677
CMD curl -sf http://localhost:8000/health || exit 1
7778

7879
# Use tini as init system
7980
ENTRYPOINT ["/usr/bin/tini", "--"]
8081

81-
# Run server
82-
CMD sh -c "python -m uvicorn lexecon.api.server:app --host 0.0.0.0 --port ${PORT:-8000}"
82+
# Run migrations then start server
83+
CMD ["/bin/sh", "/app/start.sh"]

railway.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ builder = "DOCKERFILE"
33
dockerfilePath = "Dockerfile"
44

55
[deploy]
6-
startCommand = "uvicorn lexecon.api.server:app --host 0.0.0.0 --port $PORT"
6+
startCommand = "/bin/sh /app/start.sh"
77
healthcheckPath = "/health"
88
healthcheckTimeout = 100
99
restartPolicyType = "ON_FAILURE"

src/lexecon/api/dependencies.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,26 @@ class ServiceRegistry:
4343
_instance: Optional["ServiceRegistry"] = None
4444

4545
def __init__(self) -> None:
46+
# ── Resolve data directory (honours LEXECON_DATA_DIR in Docker/Railway) ──
47+
_data_dir = os.getenv("LEXECON_DATA_DIR", ".")
48+
os.makedirs(_data_dir, exist_ok=True)
49+
50+
def _db(name: str) -> str:
51+
return os.path.join(_data_dir, name)
52+
4653
# ── Eager services (created at import time in the old code) ──
47-
self.storage: LedgerStorage = LedgerStorage("lexecon_ledger.db")
54+
self.storage: LedgerStorage = LedgerStorage(_db("lexecon_ledger.db"))
4855
self.ledger: LedgerChain = LedgerChain(storage=self.storage)
49-
self.responsibility_storage: ResponsibilityStorage = ResponsibilityStorage("lexecon_responsibility.db")
56+
self.responsibility_storage: ResponsibilityStorage = ResponsibilityStorage(_db("lexecon_responsibility.db"))
5057
self.responsibility_tracker: ResponsibilityTracker = ResponsibilityTracker(storage=self.responsibility_storage)
5158
self.node_id: str = str(uuid.uuid4())
5259
self.startup_time: float = time.time()
5360

5461
# Security services
55-
self.auth_service: AuthService = AuthService("lexecon_auth.db")
62+
self.auth_service: AuthService = AuthService(_db("lexecon_auth.db"))
5663
self.async_auth_service: AsyncAuthService = AsyncAuthService(self.auth_service)
57-
self.audit_service: AuditService = AuditService("lexecon_export_audit.db")
58-
self.signature_service: SignatureService = SignatureService("lexecon_keys")
64+
self.audit_service: AuditService = AuditService(_db("lexecon_export_audit.db"))
65+
self.signature_service: SignatureService = SignatureService(_db("lexecon_keys"))
5966

6067
# OIDC OAuth service
6168
from lexecon.security.oidc_service import get_oidc_service
@@ -106,7 +113,8 @@ def initialize(self) -> None:
106113

107114
if self.intervention_storage is None:
108115
from lexecon.compliance.eu_ai_act.storage import InterventionStorage
109-
self.intervention_storage = InterventionStorage("lexecon_interventions.db")
116+
_data_dir = os.getenv("LEXECON_DATA_DIR", ".")
117+
self.intervention_storage = InterventionStorage(os.path.join(_data_dir, "lexecon_interventions.db"))
110118

111119
if self.oversight_system is None:
112120
from lexecon.compliance.eu_ai_act.article_14_oversight import HumanOversightEvidence
@@ -134,10 +142,12 @@ def initialize(self) -> None:
134142
)
135143

136144
if self.usage_service is None:
137-
self.usage_service = UsageService("lexecon_usage.db")
145+
_data_dir = os.getenv("LEXECON_DATA_DIR", ".")
146+
self.usage_service = UsageService(os.path.join(_data_dir, "lexecon_usage.db"))
138147

139148
if self.tenancy_service is None:
140-
self.tenancy_service = TenancyService("lexecon_auth.db")
149+
_data_dir = os.getenv("LEXECON_DATA_DIR", ".")
150+
self.tenancy_service = TenancyService(os.path.join(_data_dir, "lexecon_auth.db"))
141151

142152
if self.compliance_mapping_service is None:
143153
self.compliance_mapping_service = ComplianceMappingService()

start.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/sh
2+
# Lexecon startup: run migrations then start the API server.
3+
set -e
4+
5+
DATA_DIR="${LEXECON_DATA_DIR:-.}"
6+
AUTH_DB="${DATA_DIR}/lexecon_auth.db"
7+
8+
echo "[start] data dir: ${DATA_DIR}"
9+
mkdir -p "${DATA_DIR}"
10+
11+
echo "[start] running migrations against ${AUTH_DB}..."
12+
python /app/migrations/run_all.py "${AUTH_DB}"
13+
14+
echo "[start] starting uvicorn on port ${PORT:-8000}..."
15+
exec python -m uvicorn lexecon.api.server:app \
16+
--host 0.0.0.0 \
17+
--port "${PORT:-8000}" \
18+
--workers "${LEXECON_WORKERS:-1}"

0 commit comments

Comments
 (0)