Repository navigation
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
149 lines (146 loc) · 7.49 KB
/
Copy pathdocker-compose.yml
File metadata and controls
149 lines (146 loc) · 7.49 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
# Runs on the Lightsail instance at /opt/vault-cortex/docker-compose.yml
#
# Single service: the vault-cortex :remote image bundles obsidian-sync and
# the MCP server under s6-overlay supervision — sync keeps /vault current
# (bidirectional Obsidian Sync) and the MCP server listens on :8000.
# Compose remains here as an operational wrapper (declarative restart
# policy, --wait healthcheck gating, log rotation), not a user requirement.
#
# API Gateway handles TLS + auth upstream. vault-cortex ALSO validates
# the bearer token in-process (Express middleware) as defense in
# depth — see src/vault-mcp/server.ts. Port 8000 is publicly bound
# but unauthenticated requests are rejected at the application layer.
name: vault-cortex
services:
vault-cortex:
image: ghcr.io/${GHCR_USER:?GHCR_USER required}/vault-cortex:${VAULT_CORTEX_TAG:-remote}
container_name: vault-cortex
hostname: ${DEVICE_NAME:-vault-cortex}
restart: unless-stopped
environment:
# --- Obsidian Sync (s6 init chain + svc-obsidian-sync) ---
OBSIDIAN_AUTH_TOKEN: ${OBSIDIAN_AUTH_TOKEN:-}
VAULT_NAME: "${VAULT_NAME:?Set VAULT_NAME to your Obsidian vault name (case-sensitive)}"
VAULT_PASSWORD: ${VAULT_PASSWORD:-}
PUID: ${PUID:-1000}
PGID: ${PGID:-1000}
DEVICE_NAME: ${DEVICE_NAME:-vault-cortex-lightsail}
CONFLICT_STRATEGY: ${CONFLICT_STRATEGY:-merge}
SYNC_MODE: ${SYNC_MODE:-bidirectional}
SYNC_EXCLUDED_FOLDERS: ${SYNC_EXCLUDED_FOLDERS:-}
SYNC_FILE_TYPES: ${SYNC_FILE_TYPES:-}
# Obsidian config-category sync (default: core and community plugin data,
# including daily-note settings and the Tasks plugin format). "none" disables;
# the category values are listed in .env.example.
SYNC_CONFIGS: ${SYNC_CONFIGS:-core-plugin-data,community-plugin-data}
# --- MCP server (svc-vault-mcp) ---
NODE_ENV: production
PORT: "8000"
HOST: "0.0.0.0"
VAULT_PATH: /vault
INDEX_DB_PATH: /data/index.db
MCP_AUTH_TOKEN: ${MCP_AUTH_TOKEN:?MCP_AUTH_TOKEN required for in-process auth}
PUBLIC_URL: ${PUBLIC_URL:?PUBLIC_URL required for OAuth metadata}
# These two settings choose the client IP used for OAuth rate limiting
# and request logs. API Gateway reports the visitor only in the RFC 7239
# Forwarded header (https://www.rfc-editor.org/rfc/rfc7239) and sends
# no X-Forwarded-For, so TRUST_FORWARDED_HOPS is the setting that finds
# the visitor; TRUST_PROXY_HOPS only sets how many X-Forwarded-For hops
# feed the fallback client IP, used when the Forwarded header is ignored
# or absent.
#
# What sits in front of this container TRUST_PROXY_HOPS TRUST_FORWARDED_HOPS Client IP is
# API Gateway (default) 1 1 the visitor
# Gateway → tunnel (ORIGIN_URL) that 2 0 the gateway's address
# admits any client
# Gateway → tunnel that admits only 2 1 the visitor
# the gateway
# CDN → gateway's custom domain, and 2 2 the visitor
# the CDN is the only way in
# (DISABLE_EXECUTE_API_ENDPOINT=true)
#
# On the rows where the client IP is the visitor, it comes from the
# Forwarded header and TRUST_PROXY_HOPS is not consulted. On the
# TRUST_FORWARDED_HOPS=0 row, the Forwarded header is ignored and the
# client IP is whatever TRUST_PROXY_HOPS resolves from X-Forwarded-For —
# the gateway's own address, never the visitor, since the gateway sends
# no X-Forwarded-For.
#
# A tunnel host admits only the gateway once the
# ORIGIN_ACCESS_SERVICE_TOKEN_ENABLED repo Variable is set and the Access
# policy is in place (DEPLOY.md "Port 8000 Hardening", steps 5–10). Until
# then the host passes any client-written Forwarded header through,
# which is why the "admits any client" row sets TRUST_FORWARDED_HOPS to
# 0.
#
# Port 8000 is open to the internet. With the default settings (the
# "API Gateway" row), anyone can send a fake Forwarded header straight
# to the server. To fix this, follow the steps in DEPLOY.md under "Port
# 8000 Hardening". They close the port and lock the tunnel to the
# gateway. After that, set TRUST_PROXY_HOPS=2 and TRUST_FORWARDED_HOPS=1.
TRUST_PROXY_HOPS: ${TRUST_PROXY_HOPS:-1}
TRUST_FORWARDED_HOPS: ${TRUST_FORWARDED_HOPS:-1}
LOG_LEVEL: ${LOG_LEVEL:-info}
LOG_DIR: ${LOG_DIR:-/data/logs}
LOG_RETENTION_DAYS: ${LOG_RETENTION_DAYS:-90}
TZ: ${TZ:-UTC}
EMBEDDING_ENABLED: ${EMBEDDING_ENABLED:-true}
RERANK_MODE: ${RERANK_MODE:-blended}
MEMORY_ENABLED: ${MEMORY_ENABLED:-true}
FILE_TOOLS_ENABLED: ${FILE_TOOLS_ENABLED:-true}
READONLY_MODE: ${READONLY_MODE:-false}
DISABLED_TOOLS: ${DISABLED_TOOLS:-}
WINDOWS_MODE: ${WINDOWS_MODE:-false}
MAX_FILE_BYTES: ${MAX_FILE_BYTES:-52428800}
MAX_IMAGE_OUTPUT_BYTES: ${MAX_IMAGE_OUTPUT_BYTES:-49152}
MAX_PDF_RENDER_PAGES: ${MAX_PDF_RENDER_PAGES:-5}
MEMORY_DIR: ${MEMORY_DIR:-About Me}
# Left empty = the server applies the smart defaults listed below, where <MEMORY_DIR> and
# <DAILY_NOTES_FOLDER> stand for the resolved values (defaults "About Me" and "Daily Notes").
# A set folder list replaces its whole default.
# The daily notes folder is resolved from DAILY_NOTES_FOLDER or
# .obsidian/daily-notes.json, falling back to "Daily Notes".
# PROTECTED_PATHS default: "<MEMORY_DIR>, <DAILY_NOTES_FOLDER>"
# ORPHAN_EXCLUDE_FOLDERS default: "<DAILY_NOTES_FOLDER>, Templates, <MEMORY_DIR>"
# SERVICE_DOCUMENTATION_URL default: https://github.com/aliasunder/vault-cortex
# DAILY_NOTES_FOLDER default: read from the vault's .obsidian/daily-notes.json, else "Daily Notes"
# DAILY_NOTES_FORMAT default: read from the vault's .obsidian/daily-notes.json, else "YYYY-MM-DD"
PROTECTED_PATHS: ${PROTECTED_PATHS:-}
ORPHAN_EXCLUDE_FOLDERS: ${ORPHAN_EXCLUDE_FOLDERS:-}
SERVICE_DOCUMENTATION_URL: ${SERVICE_DOCUMENTATION_URL:-}
DAILY_NOTES_FOLDER: ${DAILY_NOTES_FOLDER:-}
DAILY_NOTES_FORMAT: ${DAILY_NOTES_FORMAT:-}
volumes:
- vault_data:/vault
- mcp_index_data:/data
- obsidian_config:/home/obsidian/.config
ports:
- "0.0.0.0:8000:8000"
# start_period covers the s6 init chain (Obsidian login + sync-setup +
# first vault sync, run to completion when it succeeds) that runs before
# the MCP server boots.
# Failures during start_period don't count against retries; a fast boot
# flips healthy immediately.
healthcheck:
test:
[
"CMD",
"node",
"-e",
"fetch('http://127.0.0.1:8000/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))",
]
interval: 15s
timeout: 5s
retries: 5
start_period: 180s
logging:
driver: json-file
options: { max-size: "10m", max-file: "3" }
volumes:
# Explicit names pin these to the volumes that already exist on the
# host — a rename (or project-name drift) silently creates fresh empty
# volumes, detaching the synced vault, search index, and Obsidian
# login state.
vault_data: { name: vault-cortex_vault_data }
mcp_index_data: { name: vault-cortex_mcp_index_data }
obsidian_config: { name: vault-cortex_obsidian_config }