skillwiki 0.9.63 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-5TBIMLTZ.js +343 -0
- package/dist/chunk-C5OLZRRM.js +357 -0
- package/dist/{chunk-TUFQZ5K4.js → chunk-DR7KFHNH.js} +792 -1983
- package/dist/chunk-IZABIE44.js +647 -0
- package/dist/chunk-S5ABQCXQ.js +580 -0
- package/dist/cli.js +1540 -550
- package/dist/index-projection-ERFX76U5.js +10 -0
- package/dist/managed-write-preflight-SILEUQEV.js +11 -0
- package/dist/skillwiki-mcp.js +4 -1
- package/dist/vault-sync/scripts/lib/conflict-markers.sh +69 -0
- package/dist/vault-sync/scripts/lib/delete-intent.sh +74 -0
- package/dist/vault-sync/scripts/lib/fleet.sh +103 -0
- package/dist/vault-sync/scripts/lib/git-case.sh +71 -0
- package/dist/vault-sync/scripts/lib/git-materialization.sh +264 -0
- package/dist/vault-sync/scripts/lib/git-operation-journal.sh +469 -0
- package/dist/vault-sync/scripts/lib/git-rebase-state.sh +180 -0
- package/dist/vault-sync/scripts/lib/lockfile.sh +70 -0
- package/dist/vault-sync/scripts/lib/managed-write-lock.sh +80 -0
- package/dist/vault-sync/scripts/lib/platform.sh +184 -0
- package/dist/vault-sync/scripts/lib/runtime-manifest.sh +223 -0
- package/dist/vault-sync/scripts/wiki-fetch-notify.sh +207 -0
- package/dist/vault-sync/scripts/wiki-fuse-refresh.sh +405 -0
- package/dist/vault-sync/scripts/wiki-pull-with-auto-resolve.sh +631 -0
- package/dist/vault-sync/scripts/wiki-push.sh +364 -0
- package/dist/vault-sync/scripts/wiki-snapshot.sh +587 -0
- package/package.json +2 -2
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/README.md +13 -0
- package/skills/package.json +1 -1
- package/skills/proj-work/SKILL.md +3 -0
- package/skills/skills/proj-work/SKILL.md +3 -0
- package/skills/skills/using-skillwiki/SKILL.md +13 -0
- package/skills/skills/wiki-crystallize/SKILL.md +3 -0
- package/skills/using-skillwiki/SKILL.md +13 -0
- package/skills/wiki-crystallize/SKILL.md +3 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# wiki-fetch-notify.sh — polls origin for remote wiki changes and notifies.
|
|
3
|
+
#
|
|
4
|
+
# Runs `git fetch` against origin and emits a notification only when
|
|
5
|
+
# NEW commits have arrived since the previous run. Replaces the old
|
|
6
|
+
# seaweedfs-bisync writer with a non-destructive poller.
|
|
7
|
+
#
|
|
8
|
+
# Opt-in WIKI_FETCH_PULL_ON_DELTA=1: on positive delta, also runs
|
|
9
|
+
# `git pull --rebase` (via wiki-pull-with-auto-resolve.sh) so the local
|
|
10
|
+
# working tree consumes sg01 Snapshot commits automatically. This is a
|
|
11
|
+
# mutating action — when enabled, this script is no longer a pure reader.
|
|
12
|
+
#
|
|
13
|
+
# When pull is disabled (default), this script is read-only: fetch only
|
|
14
|
+
# touches refs/objects and is safe to run concurrently with skillwiki sync.
|
|
15
|
+
|
|
16
|
+
set -u
|
|
17
|
+
|
|
18
|
+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]:-$0}" )" && pwd )"
|
|
19
|
+
. "$SCRIPT_DIR/lib/platform.sh"
|
|
20
|
+
. "$SCRIPT_DIR/lib/lockfile.sh"
|
|
21
|
+
. "$SCRIPT_DIR/lib/git-operation-journal.sh"
|
|
22
|
+
platform_detect_os
|
|
23
|
+
|
|
24
|
+
WIKI_DIR="${WIKI_DIR:-$HOME/wiki}"
|
|
25
|
+
BRANCH="${WIKI_BRANCH:-main}"
|
|
26
|
+
STATE_DIR="$(platform_cache_dir)/wiki-fetch"
|
|
27
|
+
STATE_FILE="$STATE_DIR/last-behind"
|
|
28
|
+
STALE_STATE_FILE="$STATE_DIR/last-stale-notify"
|
|
29
|
+
STALE_NOTIFY_AFTER_SECONDS="${WIKI_FETCH_STALE_NOTIFY_AFTER_SECONDS:-1800}"
|
|
30
|
+
HANDOFF_STATE_FILE="$STATE_DIR/pending-handoff.env"
|
|
31
|
+
HANDOFF_NOTIFY_AFTER_SECONDS="${WIKI_FETCH_HANDOFF_NOTIFY_AFTER_SECONDS:-3600}"
|
|
32
|
+
case "$HANDOFF_NOTIFY_AFTER_SECONDS" in
|
|
33
|
+
''|*[!0-9]*) HANDOFF_NOTIFY_AFTER_SECONDS=3600 ;;
|
|
34
|
+
esac
|
|
35
|
+
# Opt-in: when enabled, a positive delta triggers `git pull --rebase` so the
|
|
36
|
+
# local working tree consumes sg01 Snapshot commits automatically. This
|
|
37
|
+
# replaces the git pull that was previously bundled inside wiki-push.sh's
|
|
38
|
+
# removed git-push block. Default off — set WIKI_FETCH_PULL_ON_DELTA=1 to
|
|
39
|
+
# enable on leaf hosts that want automated pull.
|
|
40
|
+
PULL_ON_DELTA="${WIKI_FETCH_PULL_ON_DELTA:-0}"
|
|
41
|
+
PULL_HELPER="$SCRIPT_DIR/wiki-pull-with-auto-resolve.sh"
|
|
42
|
+
LOG_FILE="$(platform_log_dir)/wiki-fetch.log"
|
|
43
|
+
|
|
44
|
+
mkdir -p "$STATE_DIR" "$(dirname "$LOG_FILE")"
|
|
45
|
+
|
|
46
|
+
log() {
|
|
47
|
+
printf '%s %s\n' "$(date -u +%FT%TZ)" "$*" >>"$LOG_FILE"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
handle_existing_handoff() {
|
|
51
|
+
local blocker reason op identity previous_identity notified_at now
|
|
52
|
+
blocker="$(vault_sync_op_preflight_blocker "$WIKI_DIR" 2>/dev/null || true)"
|
|
53
|
+
[ -n "$blocker" ] || { rm -f "$HANDOFF_STATE_FILE" 2>/dev/null || true; return 1; }
|
|
54
|
+
|
|
55
|
+
reason="${blocker%% *}"
|
|
56
|
+
op="${blocker#* }"
|
|
57
|
+
[ "$op" = "$blocker" ] && op=""
|
|
58
|
+
identity="${reason}:${op:-none}"
|
|
59
|
+
previous_identity=""
|
|
60
|
+
notified_at=0
|
|
61
|
+
if [ -f "$HANDOFF_STATE_FILE" ]; then
|
|
62
|
+
previous_identity="$(awk -F= '$1=="identity"{print substr($0,index($0,"=")+1)}' "$HANDOFF_STATE_FILE")"
|
|
63
|
+
notified_at="$(awk -F= '$1=="notified_at"{print $2}' "$HANDOFF_STATE_FILE")"
|
|
64
|
+
fi
|
|
65
|
+
case "$notified_at" in ''|*[!0-9]*) notified_at=0 ;; esac
|
|
66
|
+
now="$(date +%s)"
|
|
67
|
+
|
|
68
|
+
if [ "$identity" != "$previous_identity" ] || [ $((now - notified_at)) -ge "$HANDOFF_NOTIFY_AFTER_SECONDS" ]; then
|
|
69
|
+
platform_notify "wiki" "review-required handoff ${op:-none} still blocks sync"
|
|
70
|
+
printf 'identity=%s\nnotified_at=%s\n' "$identity" "$now" > "$HANDOFF_STATE_FILE"
|
|
71
|
+
log "NOTIFY handoff identity=$identity"
|
|
72
|
+
else
|
|
73
|
+
log "SKIP PULL handoff identity=$identity reminder-backoff"
|
|
74
|
+
fi
|
|
75
|
+
return 0
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
attempt_pull_on_delta() {
|
|
79
|
+
# Retry on every poll while we remain behind. This avoids the failed-pull
|
|
80
|
+
# wedge where last-behind is advanced before the pull runs and DELTA stays 0
|
|
81
|
+
# forever until a fresh upstream commit arrives.
|
|
82
|
+
if [ "$PULL_ON_DELTA" != "1" ] || [ "$BEHIND" -le 0 ]; then
|
|
83
|
+
return 0
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
if handle_existing_handoff; then
|
|
87
|
+
return 0
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
if [ -f "$WIKI_DIR/.skillwiki/sync.lock" ]; then
|
|
91
|
+
log "SKIP PULL sync lock present — leave pull to skillwiki sync"
|
|
92
|
+
return 0
|
|
93
|
+
fi
|
|
94
|
+
if [ -f "$WIKI_DIR/.git/index.lock" ] || [ -d "$WIKI_DIR/.git/rebase-merge" ] || [ -d "$WIKI_DIR/.git/rebase-apply" ] || [ -f "$WIKI_DIR/.git/MERGE_HEAD" ]; then
|
|
95
|
+
log "SKIP PULL git operation already in progress"
|
|
96
|
+
return 0
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
PULL_LOCK_FILE="$STATE_DIR/pull.lock"
|
|
100
|
+
PULL_LOCK_RC=0
|
|
101
|
+
lockfile_acquire "$PULL_LOCK_FILE" 600 || PULL_LOCK_RC=$?
|
|
102
|
+
if [ "$PULL_LOCK_RC" -eq 1 ]; then
|
|
103
|
+
log "SKIP PULL previous pull-on-delta still in flight"
|
|
104
|
+
return 0
|
|
105
|
+
elif [ "$PULL_LOCK_RC" -eq 2 ]; then
|
|
106
|
+
log "PULL stale lock reclaimed"
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
if [ -x "$PULL_HELPER" ]; then
|
|
110
|
+
if "$PULL_HELPER" origin "$BRANCH" 2>>"$LOG_FILE"; then
|
|
111
|
+
log "PULL ok via wiki-pull-with-auto-resolve"
|
|
112
|
+
else
|
|
113
|
+
log "FAIL PULL via wiki-pull-with-auto-resolve — run skillwiki sync manually"
|
|
114
|
+
fi
|
|
115
|
+
elif [ -f "$PULL_HELPER" ]; then
|
|
116
|
+
if bash "$PULL_HELPER" origin "$BRANCH" 2>>"$LOG_FILE"; then
|
|
117
|
+
log "PULL ok via bash wiki-pull-with-auto-resolve"
|
|
118
|
+
else
|
|
119
|
+
log "FAIL PULL via bash wiki-pull-with-auto-resolve — run skillwiki sync manually"
|
|
120
|
+
fi
|
|
121
|
+
elif git pull --rebase origin "$BRANCH" 2>>"$LOG_FILE"; then
|
|
122
|
+
log "PULL ok via git pull --rebase"
|
|
123
|
+
else
|
|
124
|
+
log "FAIL PULL via git pull --rebase — run skillwiki sync manually"
|
|
125
|
+
fi
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# Guard: working tree present.
|
|
129
|
+
if [ ! -d "$WIKI_DIR/.git" ]; then
|
|
130
|
+
log "ERROR: $WIKI_DIR is not a git repo"
|
|
131
|
+
exit 0
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
cd "$WIKI_DIR" || { log "ERROR: cd $WIKI_DIR failed"; exit 0; }
|
|
135
|
+
|
|
136
|
+
# Fetch only the target branch. --quiet keeps log noise low.
|
|
137
|
+
# Failure (offline, auth, etc.) is silent on the notification side.
|
|
138
|
+
if ! git fetch --quiet origin "$BRANCH" 2>>"$LOG_FILE"; then
|
|
139
|
+
log "fetch failed (offline or auth) — skipping notify"
|
|
140
|
+
exit 0
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Compute behind count. If branch is missing on either side, exit clean.
|
|
144
|
+
if ! BEHIND=$(git rev-list --count "HEAD..origin/$BRANCH" 2>>"$LOG_FILE"); then
|
|
145
|
+
log "rev-list failed — origin/$BRANCH may not exist"
|
|
146
|
+
exit 0
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
LAST_BEHIND=0
|
|
150
|
+
if [ -f "$STATE_FILE" ]; then
|
|
151
|
+
LAST_BEHIND=$(cat "$STATE_FILE" 2>/dev/null || echo 0)
|
|
152
|
+
# Sanitize: must be a non-negative integer.
|
|
153
|
+
case "$LAST_BEHIND" in
|
|
154
|
+
''|*[!0-9]*) LAST_BEHIND=0 ;;
|
|
155
|
+
esac
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
printf '%s' "$BEHIND" >"$STATE_FILE"
|
|
159
|
+
|
|
160
|
+
DELTA=$(( BEHIND - LAST_BEHIND ))
|
|
161
|
+
NOW=$(date +%s)
|
|
162
|
+
|
|
163
|
+
LAST_STALE_NOTIFY=0
|
|
164
|
+
if [ -f "$STALE_STATE_FILE" ]; then
|
|
165
|
+
LAST_STALE_NOTIFY=$(cat "$STALE_STATE_FILE" 2>/dev/null || echo 0)
|
|
166
|
+
case "$LAST_STALE_NOTIFY" in
|
|
167
|
+
''|*[!0-9]*) LAST_STALE_NOTIFY=0 ;;
|
|
168
|
+
esac
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
case "$STALE_NOTIFY_AFTER_SECONDS" in
|
|
172
|
+
''|*[!0-9]*) STALE_NOTIFY_AFTER_SECONDS=1800 ;;
|
|
173
|
+
esac
|
|
174
|
+
|
|
175
|
+
if [ "$DELTA" -gt 0 ]; then
|
|
176
|
+
# New commits arrived since last poll. Notify with delta and total.
|
|
177
|
+
TITLE="wiki"
|
|
178
|
+
if [ "$DELTA" -eq "$BEHIND" ]; then
|
|
179
|
+
MSG="$DELTA new commit(s) on origin/$BRANCH"
|
|
180
|
+
else
|
|
181
|
+
MSG="$DELTA new ($BEHIND total behind) on origin/$BRANCH"
|
|
182
|
+
fi
|
|
183
|
+
platform_notify "$TITLE" "$MSG — run skillwiki sync"
|
|
184
|
+
printf '%s' "$NOW" >"$STALE_STATE_FILE"
|
|
185
|
+
log "NOTIFY behind=$BEHIND delta=$DELTA"
|
|
186
|
+
|
|
187
|
+
elif [ "$BEHIND" -gt 0 ] && [ $(( NOW - LAST_STALE_NOTIFY )) -ge "$STALE_NOTIFY_AFTER_SECONDS" ]; then
|
|
188
|
+
TITLE="wiki"
|
|
189
|
+
MSG="still $BEHIND commit(s) behind origin/$BRANCH"
|
|
190
|
+
platform_notify "$TITLE" "$MSG — sync may be stuck"
|
|
191
|
+
printf '%s' "$NOW" >"$STALE_STATE_FILE"
|
|
192
|
+
log "NOTIFY stale behind=$BEHIND delta=$DELTA"
|
|
193
|
+
else
|
|
194
|
+
if [ "$BEHIND" -eq 0 ]; then
|
|
195
|
+
rm -f "$STALE_STATE_FILE" 2>/dev/null || true
|
|
196
|
+
fi
|
|
197
|
+
log "OK behind=$BEHIND delta=$DELTA (no notify)"
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
# Opt-in auto-pull: consume sg01 Snapshot commits into the working tree.
|
|
201
|
+
# Uses wiki-pull-with-auto-resolve.sh for conflict-storm handling. On failure,
|
|
202
|
+
# leaves the working tree behind and logs — does NOT block. When the helper is
|
|
203
|
+
# present but not executable, invoke it via bash so we still get its dirty-tree
|
|
204
|
+
# and conflict-storm handling instead of falling back to a bare git pull.
|
|
205
|
+
attempt_pull_on_delta
|
|
206
|
+
|
|
207
|
+
exit 0
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# wiki-fuse-refresh.sh — keep rclone FUSE visibility within a bounded freshness SLA.
|
|
3
|
+
#
|
|
4
|
+
# Linux-focused helper that:
|
|
5
|
+
# 1) audits active rclone mount --dir-cache-time
|
|
6
|
+
# 2) optionally forgets stale VFS directory cache entries
|
|
7
|
+
# 3) optionally triggers `rclone rc vfs/refresh recursive=true dir=/`
|
|
8
|
+
#
|
|
9
|
+
# Intended to run under systemd timer (wiki-fuse-refresh.timer) and manually via
|
|
10
|
+
# the vault-fuse-freshness skill.
|
|
11
|
+
|
|
12
|
+
set -u
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]:-$0}" )" && pwd )"
|
|
15
|
+
. "$SCRIPT_DIR/lib/platform.sh"
|
|
16
|
+
platform_detect_os
|
|
17
|
+
|
|
18
|
+
LOG_FILE="$(platform_log_dir)/wiki-fuse-refresh.log"
|
|
19
|
+
mkdir -p "$(dirname "$LOG_FILE")"
|
|
20
|
+
|
|
21
|
+
log() {
|
|
22
|
+
printf '%s %s\n' "$(date -u +%FT%TZ)" "$*" >>"$LOG_FILE"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
print() {
|
|
26
|
+
printf '[wiki-fuse-refresh] %s\n' "$*"
|
|
27
|
+
log "$*"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
lower() {
|
|
31
|
+
printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
is_true() {
|
|
35
|
+
case "$(lower "${1:-}")" in
|
|
36
|
+
1|true|yes|on) return 0 ;;
|
|
37
|
+
*) return 1 ;;
|
|
38
|
+
esac
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Parse a duration string into seconds (supports: 10, 10m, 1h30m, 500ms).
|
|
42
|
+
duration_to_seconds() {
|
|
43
|
+
local input
|
|
44
|
+
input="$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
|
|
45
|
+
[ -n "$input" ] || return 1
|
|
46
|
+
|
|
47
|
+
if printf '%s' "$input" | grep -Eq '^[0-9]+([.][0-9]+)?$'; then
|
|
48
|
+
printf '%s\n' "$input"
|
|
49
|
+
return 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
local rest="$input"
|
|
53
|
+
local total="0"
|
|
54
|
+
|
|
55
|
+
while [ -n "$rest" ]; do
|
|
56
|
+
if printf '%s' "$rest" | grep -Eq '^([0-9]+([.][0-9]+)?)(ms|s|m|h|d|w)'; then
|
|
57
|
+
local prefix
|
|
58
|
+
prefix="$(printf '%s' "$rest" | sed -E 's/^([0-9]+([.][0-9]+)?)(ms|s|m|h|d|w).*/\1 \3/')"
|
|
59
|
+
local value
|
|
60
|
+
value="$(printf '%s' "$prefix" | awk '{print $1}')"
|
|
61
|
+
local unit
|
|
62
|
+
unit="$(printf '%s' "$prefix" | awk '{print $2}')"
|
|
63
|
+
|
|
64
|
+
local factor
|
|
65
|
+
case "$unit" in
|
|
66
|
+
ms) factor="0.001" ;;
|
|
67
|
+
s) factor="1" ;;
|
|
68
|
+
m) factor="60" ;;
|
|
69
|
+
h) factor="3600" ;;
|
|
70
|
+
d) factor="86400" ;;
|
|
71
|
+
w) factor="604800" ;;
|
|
72
|
+
*) return 1 ;;
|
|
73
|
+
esac
|
|
74
|
+
|
|
75
|
+
total="$(awk -v a="$total" -v b="$value" -v f="$factor" 'BEGIN { printf "%.6f", a + (b * f) }')"
|
|
76
|
+
|
|
77
|
+
local seg_len
|
|
78
|
+
seg_len="$(printf '%s' "$rest" | sed -E 's/^([0-9]+([.][0-9]+)?)(ms|s|m|h|d|w).*/\1\3/' | awk '{print length($0)}')"
|
|
79
|
+
rest="${rest:$seg_len}"
|
|
80
|
+
else
|
|
81
|
+
return 1
|
|
82
|
+
fi
|
|
83
|
+
done
|
|
84
|
+
|
|
85
|
+
awk -v v="$total" 'BEGIN {
|
|
86
|
+
if (v + 0 == int(v + 0)) {
|
|
87
|
+
printf "%d\n", v
|
|
88
|
+
} else {
|
|
89
|
+
s = sprintf("%.6f", v)
|
|
90
|
+
sub(/0+$/, "", s)
|
|
91
|
+
sub(/[.]$/, "", s)
|
|
92
|
+
printf "%s\n", s
|
|
93
|
+
}
|
|
94
|
+
}'
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
seconds_gt() {
|
|
98
|
+
awk -v a="$1" -v b="$2" 'BEGIN { exit !(a > b) }'
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
find_rclone_mount_pid() {
|
|
102
|
+
local pid
|
|
103
|
+
pid="$(pgrep -f 'rclone.*mount' 2>/dev/null | head -n 1 || true)"
|
|
104
|
+
[ -n "$pid" ] && printf '%s\n' "$pid"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
rclone_token_stream() {
|
|
108
|
+
local pid="$1"
|
|
109
|
+
if [ -r "/proc/$pid/cmdline" ]; then
|
|
110
|
+
tr '\0' '\n' < "/proc/$pid/cmdline"
|
|
111
|
+
return 0
|
|
112
|
+
fi
|
|
113
|
+
ps -p "$pid" -o args= 2>/dev/null | tr ' ' '\n'
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
extract_flag_value() {
|
|
117
|
+
local pid="$1"
|
|
118
|
+
local flag="$2"
|
|
119
|
+
local prev=""
|
|
120
|
+
|
|
121
|
+
while IFS= read -r token; do
|
|
122
|
+
[ -n "$token" ] || continue
|
|
123
|
+
|
|
124
|
+
if [ "$prev" = "$flag" ]; then
|
|
125
|
+
printf '%s\n' "$token"
|
|
126
|
+
return 0
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
case "$token" in
|
|
130
|
+
"$flag="*)
|
|
131
|
+
printf '%s\n' "${token#*=}"
|
|
132
|
+
return 0
|
|
133
|
+
;;
|
|
134
|
+
esac
|
|
135
|
+
|
|
136
|
+
prev="$token"
|
|
137
|
+
done < <(rclone_token_stream "$pid")
|
|
138
|
+
|
|
139
|
+
return 1
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
has_flag() {
|
|
143
|
+
local pid="$1"
|
|
144
|
+
local flag="$2"
|
|
145
|
+
|
|
146
|
+
while IFS= read -r token; do
|
|
147
|
+
[ -n "$token" ] || continue
|
|
148
|
+
if [ "$token" = "$flag" ]; then
|
|
149
|
+
return 0
|
|
150
|
+
fi
|
|
151
|
+
case "$token" in
|
|
152
|
+
"$flag="*) return 0 ;;
|
|
153
|
+
esac
|
|
154
|
+
done < <(rclone_token_stream "$pid")
|
|
155
|
+
|
|
156
|
+
return 1
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
extract_mount_remote() {
|
|
160
|
+
local pid="$1"
|
|
161
|
+
local saw_mount=0
|
|
162
|
+
|
|
163
|
+
while IFS= read -r token; do
|
|
164
|
+
[ -n "$token" ] || continue
|
|
165
|
+
|
|
166
|
+
if [ "$saw_mount" -eq 0 ]; then
|
|
167
|
+
[ "$token" = "mount" ] && saw_mount=1
|
|
168
|
+
continue
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
case "$token" in
|
|
172
|
+
--*) continue ;;
|
|
173
|
+
/*) continue ;;
|
|
174
|
+
esac
|
|
175
|
+
|
|
176
|
+
case "$token" in
|
|
177
|
+
*:*)
|
|
178
|
+
printf '%s\n' "$token"
|
|
179
|
+
return 0
|
|
180
|
+
;;
|
|
181
|
+
esac
|
|
182
|
+
done < <(rclone_token_stream "$pid")
|
|
183
|
+
|
|
184
|
+
return 1
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
DRY_RUN=0
|
|
188
|
+
CHECK_ONLY=0
|
|
189
|
+
MAX_DIR_CACHE_RAW="${VS_FUSE_MAX_DIR_CACHE:-15m}"
|
|
190
|
+
RC_ADDR_OVERRIDE="${VS_FUSE_RC_ADDR:-}"
|
|
191
|
+
RC_TIMEOUT_SECONDS="${VS_FUSE_RC_TIMEOUT_SECONDS:-60}"
|
|
192
|
+
FORGET_DIRS=()
|
|
193
|
+
|
|
194
|
+
if [ -n "${VS_FUSE_FORGET_DIRS:-}" ]; then
|
|
195
|
+
read -r -a FORGET_DIRS <<< "$VS_FUSE_FORGET_DIRS"
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
usage() {
|
|
199
|
+
cat <<USAGE
|
|
200
|
+
Usage: bash wiki-fuse-refresh.sh [options]
|
|
201
|
+
|
|
202
|
+
Options:
|
|
203
|
+
--dry-run Print planned actions only
|
|
204
|
+
--check-only Audit dir-cache-time only (no rc refresh)
|
|
205
|
+
--max-dir-cache <dur> Freshness threshold (default: 15m)
|
|
206
|
+
--rc-addr <host:port> Override rc endpoint (default: from mount flags, else 127.0.0.1:5572)
|
|
207
|
+
--forget-dir <path> Forget a VFS directory cache path before refresh (repeatable)
|
|
208
|
+
--help Show this help
|
|
209
|
+
|
|
210
|
+
Environment overrides:
|
|
211
|
+
VS_DRY_RUN=1|0
|
|
212
|
+
VS_FUSE_CHECK_ONLY=1|0
|
|
213
|
+
VS_FUSE_MAX_DIR_CACHE=<duration>
|
|
214
|
+
VS_FUSE_RC_ADDR=<host:port>
|
|
215
|
+
VS_FUSE_RC_TIMEOUT_SECONDS=<seconds>
|
|
216
|
+
VS_FUSE_FORGET_DIRS="<path> [path...]"
|
|
217
|
+
USAGE
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if is_true "${VS_DRY_RUN:-0}"; then
|
|
221
|
+
DRY_RUN=1
|
|
222
|
+
fi
|
|
223
|
+
if is_true "${VS_FUSE_CHECK_ONLY:-0}"; then
|
|
224
|
+
CHECK_ONLY=1
|
|
225
|
+
fi
|
|
226
|
+
|
|
227
|
+
while [ "$#" -gt 0 ]; do
|
|
228
|
+
case "$1" in
|
|
229
|
+
--dry-run)
|
|
230
|
+
DRY_RUN=1
|
|
231
|
+
shift
|
|
232
|
+
;;
|
|
233
|
+
--check-only)
|
|
234
|
+
CHECK_ONLY=1
|
|
235
|
+
shift
|
|
236
|
+
;;
|
|
237
|
+
--max-dir-cache)
|
|
238
|
+
[ "$#" -ge 2 ] || { print "FATAL: --max-dir-cache requires a value"; exit 2; }
|
|
239
|
+
MAX_DIR_CACHE_RAW="$2"
|
|
240
|
+
shift 2
|
|
241
|
+
;;
|
|
242
|
+
--rc-addr)
|
|
243
|
+
[ "$#" -ge 2 ] || { print "FATAL: --rc-addr requires a value"; exit 2; }
|
|
244
|
+
RC_ADDR_OVERRIDE="$2"
|
|
245
|
+
shift 2
|
|
246
|
+
;;
|
|
247
|
+
--forget-dir)
|
|
248
|
+
[ "$#" -ge 2 ] || { print "FATAL: --forget-dir requires a value"; exit 2; }
|
|
249
|
+
FORGET_DIRS+=("$2")
|
|
250
|
+
shift 2
|
|
251
|
+
;;
|
|
252
|
+
--help|-h)
|
|
253
|
+
usage
|
|
254
|
+
exit 0
|
|
255
|
+
;;
|
|
256
|
+
*)
|
|
257
|
+
print "FATAL: unknown argument: $1"
|
|
258
|
+
exit 2
|
|
259
|
+
;;
|
|
260
|
+
esac
|
|
261
|
+
done
|
|
262
|
+
|
|
263
|
+
if [ "$VS_OS" != "linux" ]; then
|
|
264
|
+
print "skip: OS=$VS_OS (linux-only freshness workflow)"
|
|
265
|
+
exit 0
|
|
266
|
+
fi
|
|
267
|
+
|
|
268
|
+
if ! command -v rclone >/dev/null 2>&1; then
|
|
269
|
+
print "WARN: rclone not found in PATH"
|
|
270
|
+
exit 0
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
case "$RC_TIMEOUT_SECONDS" in
|
|
274
|
+
''|*[!0-9]*)
|
|
275
|
+
print "FATAL: VS_FUSE_RC_TIMEOUT_SECONDS must be an integer number of seconds"
|
|
276
|
+
exit 2
|
|
277
|
+
;;
|
|
278
|
+
esac
|
|
279
|
+
|
|
280
|
+
MAX_DIR_CACHE_SECONDS="$(duration_to_seconds "$MAX_DIR_CACHE_RAW" 2>/dev/null || true)"
|
|
281
|
+
if [ -z "$MAX_DIR_CACHE_SECONDS" ]; then
|
|
282
|
+
print "FATAL: cannot parse --max-dir-cache=$MAX_DIR_CACHE_RAW"
|
|
283
|
+
exit 2
|
|
284
|
+
fi
|
|
285
|
+
|
|
286
|
+
RCLONE_PID="$(find_rclone_mount_pid || true)"
|
|
287
|
+
if [ -z "$RCLONE_PID" ]; then
|
|
288
|
+
print "WARN: no running rclone mount process found"
|
|
289
|
+
exit 0
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
DIR_CACHE_RAW="$(extract_flag_value "$RCLONE_PID" --dir-cache-time 2>/dev/null || true)"
|
|
293
|
+
if [ -z "$DIR_CACHE_RAW" ]; then
|
|
294
|
+
DIR_CACHE_RAW="5m"
|
|
295
|
+
fi
|
|
296
|
+
DIR_CACHE_SECONDS="$(duration_to_seconds "$DIR_CACHE_RAW" 2>/dev/null || true)"
|
|
297
|
+
if [ -z "$DIR_CACHE_SECONDS" ]; then
|
|
298
|
+
print "WARN: cannot parse --dir-cache-time=$DIR_CACHE_RAW (pid=$RCLONE_PID)"
|
|
299
|
+
exit 1
|
|
300
|
+
fi
|
|
301
|
+
|
|
302
|
+
if seconds_gt "$DIR_CACHE_SECONDS" "$MAX_DIR_CACHE_SECONDS"; then
|
|
303
|
+
print "WARN: --dir-cache-time=$DIR_CACHE_RAW exceeds freshness threshold $MAX_DIR_CACHE_RAW (pid=$RCLONE_PID)"
|
|
304
|
+
# Keep going so refresh still runs when possible; return non-zero at end.
|
|
305
|
+
DIR_CACHE_STATUS=1
|
|
306
|
+
else
|
|
307
|
+
print "OK: --dir-cache-time=$DIR_CACHE_RAW within freshness threshold $MAX_DIR_CACHE_RAW (pid=$RCLONE_PID)"
|
|
308
|
+
DIR_CACHE_STATUS=0
|
|
309
|
+
fi
|
|
310
|
+
|
|
311
|
+
if [ "$CHECK_ONLY" -eq 1 ]; then
|
|
312
|
+
if [ "$DIR_CACHE_STATUS" -eq 0 ]; then
|
|
313
|
+
print "check-only: no refresh requested"
|
|
314
|
+
exit 0
|
|
315
|
+
fi
|
|
316
|
+
print "check-only: returning non-zero due to stale dir-cache-time"
|
|
317
|
+
exit 1
|
|
318
|
+
fi
|
|
319
|
+
|
|
320
|
+
if ! has_flag "$RCLONE_PID" --rc; then
|
|
321
|
+
print "WARN: rclone mount pid=$RCLONE_PID has no --rc flag; cannot run vfs/refresh"
|
|
322
|
+
[ "$DIR_CACHE_STATUS" -eq 0 ] && exit 0 || exit 1
|
|
323
|
+
fi
|
|
324
|
+
|
|
325
|
+
RC_ADDR="$RC_ADDR_OVERRIDE"
|
|
326
|
+
if [ -z "$RC_ADDR" ]; then
|
|
327
|
+
RC_ADDR="$(extract_flag_value "$RCLONE_PID" --rc-addr 2>/dev/null || true)"
|
|
328
|
+
fi
|
|
329
|
+
if [ -z "$RC_ADDR" ]; then
|
|
330
|
+
RC_ADDR="127.0.0.1:5572"
|
|
331
|
+
fi
|
|
332
|
+
|
|
333
|
+
RC_URL="$RC_ADDR"
|
|
334
|
+
case "$RC_URL" in
|
|
335
|
+
http://*|https://*) ;;
|
|
336
|
+
*) RC_URL="http://$RC_URL" ;;
|
|
337
|
+
esac
|
|
338
|
+
|
|
339
|
+
REMOTE="$(extract_mount_remote "$RCLONE_PID" 2>/dev/null || true)"
|
|
340
|
+
|
|
341
|
+
if ! command -v timeout >/dev/null 2>&1; then
|
|
342
|
+
print "WARN: timeout not found in PATH; cannot run bounded rclone rc operations"
|
|
343
|
+
[ "$DIR_CACHE_STATUS" -eq 0 ] && exit 1 || exit 1
|
|
344
|
+
fi
|
|
345
|
+
|
|
346
|
+
run_rclone_rc() {
|
|
347
|
+
timeout "$RC_TIMEOUT_SECONDS" rclone rc --url "$RC_URL" "$@" 2>&1
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
351
|
+
for forget_dir in "${FORGET_DIRS[@]}"; do
|
|
352
|
+
if [ -n "$REMOTE" ]; then
|
|
353
|
+
print "[dry-run] timeout $RC_TIMEOUT_SECONDS rclone rc --url $RC_URL vfs/forget dir=$forget_dir fs=$REMOTE"
|
|
354
|
+
else
|
|
355
|
+
print "[dry-run] timeout $RC_TIMEOUT_SECONDS rclone rc --url $RC_URL vfs/forget dir=$forget_dir"
|
|
356
|
+
fi
|
|
357
|
+
done
|
|
358
|
+
if [ -n "$REMOTE" ]; then
|
|
359
|
+
print "[dry-run] timeout $RC_TIMEOUT_SECONDS rclone rc --url $RC_URL vfs/refresh recursive=true dir=/ fs=$REMOTE"
|
|
360
|
+
else
|
|
361
|
+
print "[dry-run] timeout $RC_TIMEOUT_SECONDS rclone rc --url $RC_URL vfs/refresh recursive=true dir=/"
|
|
362
|
+
fi
|
|
363
|
+
[ "$DIR_CACHE_STATUS" -eq 0 ] && exit 0 || exit 1
|
|
364
|
+
fi
|
|
365
|
+
|
|
366
|
+
for forget_dir in "${FORGET_DIRS[@]}"; do
|
|
367
|
+
if [ -n "$REMOTE" ]; then
|
|
368
|
+
RC_OUT="$(run_rclone_rc vfs/forget "dir=$forget_dir" "fs=$REMOTE")"
|
|
369
|
+
else
|
|
370
|
+
RC_OUT="$(run_rclone_rc vfs/forget "dir=$forget_dir")"
|
|
371
|
+
fi
|
|
372
|
+
RC_CODE=$?
|
|
373
|
+
|
|
374
|
+
if [ "$RC_CODE" -ne 0 ]; then
|
|
375
|
+
print "FAIL: vfs/forget dir=$forget_dir rc=$RC_CODE url=$RC_URL"
|
|
376
|
+
log "$RC_OUT"
|
|
377
|
+
[ "$DIR_CACHE_STATUS" -eq 0 ] && exit "$RC_CODE" || exit 1
|
|
378
|
+
fi
|
|
379
|
+
|
|
380
|
+
print "OK: vfs/forget dir=$forget_dir completed via $RC_URL"
|
|
381
|
+
done
|
|
382
|
+
|
|
383
|
+
if [ -n "$REMOTE" ]; then
|
|
384
|
+
RC_OUT="$(run_rclone_rc vfs/refresh recursive=true dir=/ "fs=$REMOTE")"
|
|
385
|
+
else
|
|
386
|
+
RC_OUT="$(run_rclone_rc vfs/refresh recursive=true dir=/)"
|
|
387
|
+
fi
|
|
388
|
+
RC_CODE=$?
|
|
389
|
+
|
|
390
|
+
if [ "$RC_CODE" -ne 0 ]; then
|
|
391
|
+
if printf '%s\n' "$RC_OUT" | grep -qi 'file does not exist'; then
|
|
392
|
+
print "WARN: root vfs/refresh with dir=/ failed; retrying without dir=/"
|
|
393
|
+
RC_OUT="$(run_rclone_rc vfs/refresh recursive=true)"
|
|
394
|
+
RC_CODE=$?
|
|
395
|
+
fi
|
|
396
|
+
fi
|
|
397
|
+
|
|
398
|
+
if [ "$RC_CODE" -ne 0 ]; then
|
|
399
|
+
print "FAIL: vfs/refresh rc=$RC_CODE url=$RC_URL"
|
|
400
|
+
log "$RC_OUT"
|
|
401
|
+
[ "$DIR_CACHE_STATUS" -eq 0 ] && exit "$RC_CODE" || exit 1
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
print "OK: vfs/refresh completed via $RC_URL"
|
|
405
|
+
[ "$DIR_CACHE_STATUS" -eq 0 ] && exit 0 || exit 1
|