文件预览

webdav.sh

查看 ClawSync 技能包中的文件内容。

文件内容

providers/webdav.sh

#!/usr/bin/env bash
# ClawSync Provider — WebDAV (Nextcloud, ownCloud, etc., via rclone)
set -euo pipefail
VAULT_DIR="$HOME/.clawsync"; CONFIG="$VAULT_DIR/config.yaml"; PROVIDER_CONFIG="$VAULT_DIR/.provider-webdav.json"
RCLONE_REMOTE="clawsync-webdav"; REMOTE_DIR="ClawSync"
timestamp() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }; log() { echo "[clawsync:webdav $(timestamp)] $*"; }

ensure_rclone() {
  if ! command -v rclone &>/dev/null; then
    log "rclone not found. Installing..."
    case "$(uname -s)" in Darwin) brew install rclone 2>/dev/null || curl https://rclone.org/install.sh | bash ;; Linux) curl https://rclone.org/install.sh | sudo bash ;; esac
  fi
}

EXCLUDE="--exclude local/** --exclude keys/** --exclude .provider-*.json --exclude .cloud-provider.json --exclude .sync-* --exclude .pull-* --exclude .heartbeat.pid --exclude .git-local/** --exclude .git/**"

get_profile_name() {
  if [[ -n "${CLAWSYNC_PROFILE:-}" ]]; then echo "$CLAWSYNC_PROFILE"; return; fi
  local name
  name=$(grep 'profile_name:' "$CONFIG" 2>/dev/null | head -1 | awk '{print $2}' | tr -d '"')
  echo "${name:-$(hostname -s 2>/dev/null || echo default)}"
}

cmd_setup() {
  echo ""; echo "WebDAV Setup"; echo "============"
  echo "Works with Nextcloud, ownCloud, and any WebDAV server."
  echo ""
  read -rp "WebDAV URL (e.g. https://cloud.example.com/remote.php/dav/files/user): " url
  read -rp "Username: " user
  read -rsp "Password: " pass; echo ""
  read -rp "Remote directory [$REMOTE_DIR]: " rdir; rdir="${rdir:-$REMOTE_DIR}"

  if [[ -z "$url" || -z "$user" || -z "$pass" ]]; then
    log "URL, username, and password are required."; return 1
  fi

  ensure_rclone
  rclone config create "$RCLONE_REMOTE" webdav \
    url="$url" vendor="other" user="$user" pass="$(rclone obscure "$pass" 2>/dev/null || echo "$pass")" 2>/dev/null || { log "Running interactive rclone config..."; rclone config; }
  rclone mkdir "${RCLONE_REMOTE}:${rdir}" 2>/dev/null || true

  cat > "$PROVIDER_CONFIG" <<JSON
{"provider":"webdav","url":"$url","user":"$user","remote_dir":"$rdir","configured":"$(timestamp)"}
JSON
  log "WebDAV configured → $url/$rdir"
}

cmd_push() {
  ensure_rclone
  local rdir
  rdir=$(python3 -c "import json;print(json.load(open('$PROVIDER_CONFIG'))['remote_dir'])" 2>/dev/null || echo "$REMOTE_DIR")
  local profile_name; profile_name=$(get_profile_name)
  local remote="${RCLONE_REMOTE}:${rdir}/profiles/$profile_name"
  log "Pushing to WebDAV (profile: $profile_name)..."
  rclone sync "$VAULT_DIR" "$remote" $EXCLUDE -v 2>&1 | grep -E "Transferred|Elapsed" || true
  log "Push to WebDAV complete (profile: $profile_name)"
}

cmd_pull() {
  ensure_rclone; local d="$VAULT_DIR/.pull-webdav"; mkdir -p "$d"
  local rdir
  rdir=$(python3 -c "import json;print(json.load(open('$PROVIDER_CONFIG'))['remote_dir'])" 2>/dev/null || echo "$REMOTE_DIR")
  local profile_name; profile_name=$(get_profile_name)
  local remote="${RCLONE_REMOTE}:${rdir}/profiles/$profile_name"
  # Fallback to root if profiles/ doesn't exist
  if ! rclone lsd "${RCLONE_REMOTE}:${rdir}/profiles" &>/dev/null; then
    remote="${RCLONE_REMOTE}:${rdir}"
    log "No profiles found, falling back to root..."
  fi
  log "Pulling from WebDAV (profile: $profile_name)..."
  rclone sync "$remote" "$d" -v 2>&1 | grep -E "Transferred|Elapsed" || true
  for f in identity/USER.md knowledge/MEMORY.md requirements.yaml manifest.json identity/instances.yaml; do
    [[ -f "$d/$f" ]] && mkdir -p "$(dirname "$VAULT_DIR/$f")" && cp "$d/$f" "$VAULT_DIR/$f"
  done
  [[ -d "$d/knowledge/projects" ]] && mkdir -p "$VAULT_DIR/knowledge/projects" && cp -r "$d/knowledge/projects/"* "$VAULT_DIR/knowledge/projects/" 2>/dev/null || true
  rm -rf "$d"; log "Pull from WebDAV complete (profile: $profile_name)"
}

cmd_test() {
  ensure_rclone; log "Testing WebDAV..."
  rclone lsd "${RCLONE_REMOTE}:" &>/dev/null && log "Connected to WebDAV" || log "Cannot reach WebDAV. Re-run setup."
}

cmd_list_profiles() {
  ensure_rclone
  local rdir
  rdir=$(python3 -c "import json;print(json.load(open('$PROVIDER_CONFIG'))['remote_dir'])" 2>/dev/null || echo "$REMOTE_DIR")
  local current; current=$(get_profile_name)
  echo ""; echo "Remote Profiles"; echo "==============="
  if rclone lsd "${RCLONE_REMOTE}:${rdir}/profiles/" &>/dev/null; then
    rclone lsd "${RCLONE_REMOTE}:${rdir}/profiles/" 2>/dev/null | awk '{print $NF}' | while read -r p; do
      local marker=""; [[ "$p" == "$current" ]] && marker=" ← current"
      echo "  $p$marker"
    done
  else
    echo "  (no profiles yet)"
  fi
  echo ""
}

cmd_info() {
  if [[ -f "$PROVIDER_CONFIG" ]]; then
    local url rdir
    url=$(python3 -c "import json;print(json.load(open('$PROVIDER_CONFIG'))['url'])" 2>/dev/null || echo "?")
    rdir=$(python3 -c "import json;print(json.load(open('$PROVIDER_CONFIG'))['remote_dir'])" 2>/dev/null || echo "?")
    echo "  Remote: $url/$rdir (via rclone)"
    echo "  Profile: $(get_profile_name)"
  else echo "  Not configured"; fi
}

case "${1:-info}" in setup) cmd_setup;; push) cmd_push;; pull) cmd_pull;; test) cmd_test;; info) cmd_info;; list-profiles) cmd_list_profiles;; esac