Sync Obsidian both ways so notes Simba writes reach your devices #86
@@ -195,6 +195,10 @@ OBSIDIAN_E2E_PASSWORD=
|
|||||||
OBSIDIAN_DEVICE_NAME=simbarag
|
OBSIDIAN_DEVICE_NAME=simbarag
|
||||||
# Set to true to run continuous sync in the background
|
# Set to true to run continuous sync in the background
|
||||||
OBSIDIAN_CONTINUOUS_SYNC=false
|
OBSIDIAN_CONTINUOUS_SYNC=false
|
||||||
|
# bidirectional: notes and journal tasks Simba writes sync back to your devices.
|
||||||
|
# pull-only: download only, Simba's writes stay in the container's copy.
|
||||||
|
# mirror-remote: download only and revert local changes.
|
||||||
|
OBSIDIAN_SYNC_MODE=bidirectional
|
||||||
OBSIDIAN_SYNC_INTERVAL=60
|
OBSIDIAN_SYNC_INTERVAL=60
|
||||||
# Local path to Obsidian vault (where files are synced)
|
# Local path to Obsidian vault (where files are synced)
|
||||||
OBSIDIAN_VAULT_PATH=/app/data/obsidian
|
OBSIDIAN_VAULT_PATH=/app/data/obsidian
|
||||||
|
|||||||
@@ -225,6 +225,14 @@ def validate_configuration(env: Mapping[str, str], *, production: bool = True) -
|
|||||||
problem(flag, "signature validation must remain enabled in production")
|
problem(flag, "signature validation must remain enabled in production")
|
||||||
for name in ("METRICS_TOKEN", "CRON_SECRET", "EMAIL_HMAC_SECRET"):
|
for name in ("METRICS_TOKEN", "CRON_SECRET", "EMAIL_HMAC_SECRET"):
|
||||||
secret(name, minimum=32)
|
secret(name, minimum=32)
|
||||||
|
if env.get("OBSIDIAN_SYNC_MODE", "bidirectional") not in {
|
||||||
|
"bidirectional",
|
||||||
|
"pull-only",
|
||||||
|
"mirror-remote",
|
||||||
|
}:
|
||||||
|
problem(
|
||||||
|
"OBSIDIAN_SYNC_MODE", "must be bidirectional, pull-only or mirror-remote"
|
||||||
|
)
|
||||||
if env.get("OBSIDIAN_CONTINUOUS_SYNC") == "true":
|
if env.get("OBSIDIAN_CONTINUOUS_SYNC") == "true":
|
||||||
for name in ("OBSIDIAN_EMAIL", "OBSIDIAN_PASSWORD", "OBSIDIAN_VAULT_ID"):
|
for name in ("OBSIDIAN_EMAIL", "OBSIDIAN_PASSWORD", "OBSIDIAN_VAULT_ID"):
|
||||||
if not value(name):
|
if not value(name):
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ x-raggr-common: &raggr-common
|
|||||||
- OBSIDIAN_E2E_PASSWORD=${OBSIDIAN_E2E_PASSWORD}
|
- OBSIDIAN_E2E_PASSWORD=${OBSIDIAN_E2E_PASSWORD}
|
||||||
- OBSIDIAN_DEVICE_NAME=${OBSIDIAN_DEVICE_NAME}
|
- OBSIDIAN_DEVICE_NAME=${OBSIDIAN_DEVICE_NAME}
|
||||||
- OBSIDIAN_CONTINUOUS_SYNC=${OBSIDIAN_CONTINUOUS_SYNC:-false}
|
- OBSIDIAN_CONTINUOUS_SYNC=${OBSIDIAN_CONTINUOUS_SYNC:-false}
|
||||||
|
- OBSIDIAN_SYNC_MODE=${OBSIDIAN_SYNC_MODE:-bidirectional}
|
||||||
- OBSIDIAN_VAULT_PATH=${OBSIDIAN_VAULT_PATH:-/app/data/obsidian}
|
- OBSIDIAN_VAULT_PATH=${OBSIDIAN_VAULT_PATH:-/app/data/obsidian}
|
||||||
- OBSIDIAN_VAULT_NAME=${OBSIDIAN_VAULT_NAME:-}
|
- OBSIDIAN_VAULT_NAME=${OBSIDIAN_VAULT_NAME:-}
|
||||||
- S3_ENDPOINT_URL=${S3_ENDPOINT_URL}
|
- S3_ENDPOINT_URL=${S3_ENDPOINT_URL}
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ make logs
|
|||||||
make migrate-history
|
make migrate-history
|
||||||
```
|
```
|
||||||
|
|
||||||
Startup validates configuration, runs `aerich upgrade`, optionally sets up Obsidian pull-only sync, and replaces the shell with the Python process. **A migration failure stops startup; the application is never launched.** Obsidian login/setup failures still log a warning and allow the application to continue.
|
Startup validates configuration, runs `aerich upgrade`, optionally starts Obsidian sync in the background (`OBSIDIAN_SYNC_MODE`, default `bidirectional` so notes and tasks Simba writes reach your devices; containers take turns via a lock file in the shared vault), and replaces the shell with the Python process. **A migration failure stops startup; the application is never launched.** Obsidian login/setup failures still log a warning and allow the application to continue.
|
||||||
|
|
||||||
An empty database without Aerich metadata may need explicit initialization. Inspect its state first. Run the migration tool in a one-off container because the app cannot start until migrations pass:
|
An empty database without Aerich metadata may need explicit initialization. Inspect its state first. Run the migration tool in a one-off container because the app cannot start until migrations pass:
|
||||||
|
|
||||||
|
|||||||
+28
-19
@@ -19,27 +19,36 @@ if [ "${OBSIDIAN_CONTINUOUS_SYNC:-false}" = "true" ]; then
|
|||||||
if [ -z "${OBSIDIAN_EMAIL:-}" ] || [ -z "${OBSIDIAN_PASSWORD:-}" ] || [ -z "${OBSIDIAN_VAULT_ID:-}" ]; then
|
if [ -z "${OBSIDIAN_EMAIL:-}" ] || [ -z "${OBSIDIAN_PASSWORD:-}" ] || [ -z "${OBSIDIAN_VAULT_ID:-}" ]; then
|
||||||
echo "WARNING: OBSIDIAN_EMAIL, OBSIDIAN_PASSWORD, or OBSIDIAN_VAULT_ID not set. Skipping sync."
|
echo "WARNING: OBSIDIAN_EMAIL, OBSIDIAN_PASSWORD, or OBSIDIAN_VAULT_ID not set. Skipping sync."
|
||||||
else
|
else
|
||||||
echo "Setting up Obsidian sync..."
|
|
||||||
|
|
||||||
VAULT_PATH="${OBSIDIAN_VAULT_PATH:-/app/data/obsidian}"
|
VAULT_PATH="${OBSIDIAN_VAULT_PATH:-/app/data/obsidian}"
|
||||||
|
SYNC_MODE="${OBSIDIAN_SYNC_MODE:-bidirectional}"
|
||||||
|
mkdir -p "${VAULT_PATH}"
|
||||||
|
|
||||||
# Login and setup sync (foreground, must complete before sync starts)
|
# Blue and green share the vault volume. flock on a file in it works
|
||||||
if ob login --email "${OBSIDIAN_EMAIL}" --password "${OBSIDIAN_PASSWORD}" && \
|
# across containers, so this container only sets up and runs sync once
|
||||||
ob sync-setup \
|
# the previous container's sync process has exited. Two clients writing
|
||||||
--vault "${OBSIDIAN_VAULT_ID}" \
|
# one vault and its sync state would otherwise race. Setup runs in the
|
||||||
--path "${VAULT_PATH}" \
|
# background so the app starts while it waits.
|
||||||
--password "${OBSIDIAN_E2E_PASSWORD:-}" \
|
(
|
||||||
--device-name "${OBSIDIAN_DEVICE_NAME:-simbarag}"; then
|
flock 9
|
||||||
# Remove stale lock from previous container run
|
echo "Setting up Obsidian sync (${SYNC_MODE})..."
|
||||||
rm -rf "${VAULT_PATH}/.obsidian/.sync.lock"
|
if ob login --email "${OBSIDIAN_EMAIL}" --password "${OBSIDIAN_PASSWORD}" && \
|
||||||
# Set sync to pull-only (read-only) mode
|
ob sync-setup \
|
||||||
ob sync-config --mode pull-only --path "${VAULT_PATH}"
|
--vault "${OBSIDIAN_VAULT_ID}" \
|
||||||
# Start continuous sync in background
|
--path "${VAULT_PATH}" \
|
||||||
echo "Starting Obsidian continuous sync (pull-only)..."
|
--password "${OBSIDIAN_E2E_PASSWORD:-}" \
|
||||||
ob sync --continuous --path "${VAULT_PATH}" &
|
--device-name "${OBSIDIAN_DEVICE_NAME:-simbarag}"; then
|
||||||
else
|
# Safe now: the lock means no other container's sync is running.
|
||||||
echo "WARNING: Obsidian sync setup failed. Continuing without sync."
|
rm -rf "${VAULT_PATH}/.obsidian/.sync.lock"
|
||||||
fi
|
if ob sync-config --mode "${SYNC_MODE}" --conflict-strategy merge \
|
||||||
|
--path "${VAULT_PATH}"; then
|
||||||
|
echo "Starting Obsidian continuous sync (${SYNC_MODE})..."
|
||||||
|
exec ob sync --continuous --path "${VAULT_PATH}"
|
||||||
|
fi
|
||||||
|
echo "WARNING: Obsidian sync-config failed. Continuing without sync."
|
||||||
|
else
|
||||||
|
echo "WARNING: Obsidian sync setup failed. Continuing without sync."
|
||||||
|
fi
|
||||||
|
) 9>"${VAULT_PATH}/.raggr-sync.lock" &
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ def test_missing_required_settings(production_env, name):
|
|||||||
("CHECKPOINT_DB_PATH", "/data/invalid\x00.db"),
|
("CHECKPOINT_DB_PATH", "/data/invalid\x00.db"),
|
||||||
("CONVERSATION_SUMMARY_DB_PATH", ""),
|
("CONVERSATION_SUMMARY_DB_PATH", ""),
|
||||||
("OBSIDIAN_CONTINUOUS_SYNC", "yes"),
|
("OBSIDIAN_CONTINUOUS_SYNC", "yes"),
|
||||||
|
("OBSIDIAN_SYNC_MODE", "push-only"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_malformed_and_unsafe_settings(production_env, name, value):
|
def test_malformed_and_unsafe_settings(production_env, name, value):
|
||||||
@@ -202,6 +203,7 @@ def test_compose_forwards_runtime_settings():
|
|||||||
"CHECKPOINT_DB_PATH",
|
"CHECKPOINT_DB_PATH",
|
||||||
"CONVERSATION_SUMMARY_DB_PATH",
|
"CONVERSATION_SUMMARY_DB_PATH",
|
||||||
"OBSIDIAN_SYNC_INTERVAL",
|
"OBSIDIAN_SYNC_INTERVAL",
|
||||||
|
"OBSIDIAN_SYNC_MODE",
|
||||||
"OIDC_AUTHORIZATION_ENDPOINT",
|
"OIDC_AUTHORIZATION_ENDPOINT",
|
||||||
"OIDC_TOKEN_ENDPOINT",
|
"OIDC_TOKEN_ENDPOINT",
|
||||||
"OIDC_USERINFO_ENDPOINT",
|
"OIDC_USERINFO_ENDPOINT",
|
||||||
|
|||||||
Reference in New Issue
Block a user