diff --git a/config/openclaw/openclaw.template.json b/config/openclaw/openclaw.template.json index b7e01fd37..e85ee468b 100644 --- a/config/openclaw/openclaw.template.json +++ b/config/openclaw/openclaw.template.json @@ -1052,7 +1052,7 @@ "memory-wiki": { "enabled": true, "config": { - "vaultMode": "unsafe-local", + "vaultMode": "bridge", "vault": { "path": "__HOME__/ghq/github.com/shunkakinoki/wiki", "renderMode": "obsidian" @@ -1063,11 +1063,13 @@ "vaultName": "Shun Kakinoki", "openAfterWrites": false }, - "unsafeLocal": { - "allowPrivateMemoryCoreAccess": true, - "paths": [ - "__HOME__/.openclaw/workspace" - ] + "bridge": { + "enabled": true, + "readMemoryArtifacts": true, + "indexDreamReports": true, + "indexDailyNotes": true, + "indexMemoryRoot": true, + "followMemoryEvents": true }, "ingest": { "autoCompile": true, diff --git a/config/openclaw/openclaw.tpl.json b/config/openclaw/openclaw.tpl.json index 56a8d6906..8c81fa57b 100644 --- a/config/openclaw/openclaw.tpl.json +++ b/config/openclaw/openclaw.tpl.json @@ -1052,7 +1052,7 @@ "memory-wiki": { "enabled": true, "config": { - "vaultMode": "unsafe-local", + "vaultMode": "bridge", "vault": { "path": "__HOME__/ghq/github.com/shunkakinoki/wiki", "renderMode": "obsidian" @@ -1063,11 +1063,13 @@ "vaultName": "Shun Kakinoki", "openAfterWrites": false }, - "unsafeLocal": { - "allowPrivateMemoryCoreAccess": true, - "paths": [ - "__HOME__/.openclaw/workspace" - ] + "bridge": { + "enabled": true, + "readMemoryArtifacts": true, + "indexDreamReports": true, + "indexDailyNotes": true, + "indexMemoryRoot": true, + "followMemoryEvents": true }, "ingest": { "autoCompile": true, diff --git a/home-manager/services/obsidian/default.nix b/home-manager/services/obsidian/default.nix index d2eef61dc..c144d830f 100644 --- a/home-manager/services/obsidian/default.nix +++ b/home-manager/services/obsidian/default.nix @@ -25,14 +25,14 @@ let ) ); - # Trigger obsidian-git's auto-backup via CDP. The Electron renderer's - # setTimeout doesn't fire under headless xvfb (futex blocks the event - # loop), but CDP uses IPC and bypasses it. All git operations still - # run through the obsidian-git plugin. - obsidianGitTrigger = pkgs.writeShellScriptBin "obsidian-git-trigger" ( + # Sync the vault directly with Git. Headless Obsidian does not reliably + # load community plugins, so Git durability must not depend on its renderer. + wikiGitSync = pkgs.writeShellScriptBin "wiki-git-sync" ( builtins.readFile ( pkgs.replaceVars ./obsidian-git-trigger.sh { - inherit (pkgs) curl jq websocat; + inherit (pkgs) coreutils git; + utilLinux = pkgs.util-linux; + vaultDir = "${homeDir}/ghq/github.com/shunkakinoki/wiki"; } ) ); @@ -64,19 +64,19 @@ lib.mkIf host.isKyber { systemd.user.services.obsidian-git-trigger = { Unit = { - Description = "Trigger obsidian-git auto-backup via CDP"; - After = [ "obsidian.service" ]; + Description = "Commit and push the memory wiki vault"; + After = [ "network-online.target" ]; X-SwitchMethod = "keep-old"; }; Service = { Type = "oneshot"; - ExecStart = "${obsidianGitTrigger}/bin/obsidian-git-trigger"; + ExecStart = "${wikiGitSync}/bin/wiki-git-sync"; }; }; systemd.user.timers.obsidian-git-trigger = { Unit = { - Description = "Trigger obsidian-git auto-backup every 3 minutes"; + Description = "Commit and push the memory wiki every 3 minutes"; }; Timer = { OnBootSec = "2min"; diff --git a/home-manager/services/obsidian/obsidian-git-trigger.sh b/home-manager/services/obsidian/obsidian-git-trigger.sh index eb92096f8..7c2c5d0bc 100644 --- a/home-manager/services/obsidian/obsidian-git-trigger.sh +++ b/home-manager/services/obsidian/obsidian-git-trigger.sh @@ -1,21 +1,37 @@ #!/usr/bin/env bash -# Trigger obsidian-git's commitAndSync via CDP. -# -# The Electron renderer's setTimeout doesn't fire under headless xvfb -# (futex_wait_queue blocks the event loop pump), but CDP messages use -# IPC and bypass the stuck loop. We trigger the backup and keep the -# websocket open with ping-interval for 15s to pump the event loop -# while git operations complete through the obsidian-git plugin. - -CDP="http://localhost:9222" - -WS_URL=$(@curl@/bin/curl -sf "$CDP/json" | @jq@/bin/jq -r '.[0].webSocketDebuggerUrl // empty') -[ -z "$WS_URL" ] && exit 0 - -# Send trigger, then keep stdin open for 15s so websocat stays alive. -# The --ping-interval sends websocket pings that pump the Electron -# event loop, allowing the obsidian-git promise queue to execute. -{ - echo '{"id":1,"method":"Runtime.evaluate","params":{"expression":"app.plugins.plugins['"'"'obsidian-git'"'"']?.automaticsManager?.doAutoCommitAndSync()"}}' - sleep 15 -} | @websocat@/bin/websocat --ping-interval 1 "$WS_URL" >/dev/null 2>&1 || true +# Commit, rebase, and push the memory wiki without depending on Obsidian's +# headless renderer or the obsidian-git community plugin. + +set -euo pipefail + +VAULT="@vaultDir@" +GIT="@git@/bin/git" + +if ! "$GIT" -C "$VAULT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "wiki-git-sync: vault is not a Git checkout: $VAULT" >&2 + exit 1 +fi + +BRANCH=$("$GIT" -C "$VAULT" symbolic-ref --short HEAD) +if [ "$BRANCH" != "main" ]; then + echo "wiki-git-sync: expected main branch, found $BRANCH" >&2 + exit 1 +fi + +exec 9>"$VAULT/.git/wiki-sync.lock" +if ! @utilLinux@/bin/flock -n 9; then + exit 0 +fi + +"$GIT" -C "$VAULT" add -A + +if ! "$GIT" -C "$VAULT" diff --cached --quiet; then + "$GIT" -C "$VAULT" -c commit.gpgsign=false commit -m "vault backup: $(@coreutils@/bin/date -u '+%Y-%m-%d %H:%M:%S UTC')" +fi + +"$GIT" -C "$VAULT" fetch origin main +"$GIT" -C "$VAULT" rebase origin/main + +if [ "$("$GIT" -C "$VAULT" rev-parse HEAD)" != "$("$GIT" -C "$VAULT" rev-parse origin/main)" ]; then + "$GIT" -C "$VAULT" push origin HEAD:main +fi diff --git a/spec/obsidian_git_trigger_spec.sh b/spec/obsidian_git_trigger_spec.sh index 28195c01f..7c8c4045a 100644 --- a/spec/obsidian_git_trigger_spec.sh +++ b/spec/obsidian_git_trigger_spec.sh @@ -11,37 +11,42 @@ The output should include '#!/usr/bin/env bash' End It 'passes bash syntax check after replacing placeholders' -When run bash -c "sed -e 's|@curl@|/usr|g' -e 's|@jq@|/usr|g' -e 's|@websocat@|/usr|g' '$SCRIPT' | bash -n" +When run bash -c "sed -e 's|@coreutils@|/usr|g' -e 's|@git@|/usr|g' -e 's|@utilLinux@|/usr|g' -e 's|@vaultDir@|/tmp/wiki|g' '$SCRIPT' | bash -n" The status should be success End End Describe 'placeholder references' -It 'references curl via placeholder' -When run bash -c "grep '@curl@' '$SCRIPT'" -The output should include '@curl@' +It 'references git via placeholder' +When run bash -c "grep '@git@' '$SCRIPT'" +The output should include '@git@' End -It 'references jq via placeholder' -When run bash -c "grep '@jq@' '$SCRIPT'" -The output should include '@jq@' +It 'references flock via placeholder' +When run bash -c "grep '@utilLinux@' '$SCRIPT'" +The output should include '@utilLinux@' +End End -It 'references websocat via placeholder' -When run bash -c "grep '@websocat@' '$SCRIPT'" -The output should include '@websocat@' +Describe 'Git synchronization' +It 'serializes overlapping timer runs' +When run bash -c "grep 'flock -n' '$SCRIPT'" +The output should include 'flock -n' End + +It 'rebases before pushing main' +When run bash -c "grep 'rebase origin/main' '$SCRIPT'" +The output should include 'rebase origin/main' End -Describe 'CDP integration' -It 'connects to CDP on port 9222' -When run bash -c "grep '9222' '$SCRIPT'" -The output should include '9222' +It 'disables interactive signing for unattended commits' +When run bash -c "grep 'commit.gpgsign=false' '$SCRIPT'" +The output should include 'commit.gpgsign=false' End -It 'triggers doAutoCommitAndSync' -When run bash -c "grep 'doAutoCommitAndSync' '$SCRIPT'" -The output should include 'doAutoCommitAndSync' +It 'fails when the vault is not a Git checkout' +When run bash -c "grep 'vault is not a Git checkout' '$SCRIPT'" +The output should include 'vault is not a Git checkout' End End