|
|
|
@ -1,11 +1,18 @@
|
|
|
|
#!/usr/bin/env bash
|
|
|
|
#!/usr/bin/env bash
|
|
|
|
# Install dsync from https://git.ai.infran.ru/ilyukhin/dsync.git
|
|
|
|
# Install dsync from https://git.ai.infran.ru/ilyukhin/dsync.git
|
|
|
|
#
|
|
|
|
#
|
|
|
|
|
|
|
|
# Behavior:
|
|
|
|
|
|
|
|
# - Fetch latest dsync from repo (git shallow clone or raw download main/master).
|
|
|
|
|
|
|
|
# - Compare VERSION in remote vs locally installed (DEST_PATH).
|
|
|
|
|
|
|
|
# * If equal and no --force: say "already up to date" and exit 0.
|
|
|
|
|
|
|
|
# * If remote newer: update.
|
|
|
|
|
|
|
|
# * If local newer and no --force: warn and exit 1 (avoid accidental downgrade).
|
|
|
|
|
|
|
|
#
|
|
|
|
# Usage:
|
|
|
|
# Usage:
|
|
|
|
# ./install_dsync.sh # install to ~/.local/bin (default)
|
|
|
|
# ./install_dsync.sh # install to ~/.local/bin (default)
|
|
|
|
# ./install_dsync.sh --system # install to /usr/local/bin (requires sudo)
|
|
|
|
# ./install_dsync.sh --system # install to /usr/local/bin (requires sudo)
|
|
|
|
# ./install_dsync.sh -d DIR # install to custom DIR
|
|
|
|
# ./install_dsync.sh -d DIR # install to custom DIR
|
|
|
|
# ./install_dsync.sh -f # force overwrite if target exists
|
|
|
|
# ./install_dsync.sh -f # force overwrite (also allows downgrade/reinstall)
|
|
|
|
# ./install_dsync.sh -h # show help
|
|
|
|
# ./install_dsync.sh -h # show help
|
|
|
|
set -euo pipefail
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
|
|
@ -23,12 +30,58 @@ Install dsync from ${REPO_URL}
|
|
|
|
Options:
|
|
|
|
Options:
|
|
|
|
-s, --system install into /usr/local/bin (system-wide; may use sudo)
|
|
|
|
-s, --system install into /usr/local/bin (system-wide; may use sudo)
|
|
|
|
-d DIR, --dest DIR install into DIR
|
|
|
|
-d DIR, --dest DIR install into DIR
|
|
|
|
-f, --force overwrite existing file without asking
|
|
|
|
-f, --force overwrite existing file; also bypasses version equality/downgrade checks
|
|
|
|
-h, --help show this help
|
|
|
|
-h, --help show this help
|
|
|
|
EOF
|
|
|
|
EOF
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# parse options
|
|
|
|
# --- version helpers ----------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
extract_version() {
|
|
|
|
|
|
|
|
# Parse VERSION from a dsync file (Python). Prints version or nothing.
|
|
|
|
|
|
|
|
# Accepts forms like: VERSION = "0.2.1"
|
|
|
|
|
|
|
|
local f="$1"
|
|
|
|
|
|
|
|
[[ -f "$f" ]] || { echo ""; return 1; }
|
|
|
|
|
|
|
|
# get line with VERSION=
|
|
|
|
|
|
|
|
local line
|
|
|
|
|
|
|
|
line="$(grep -E '^[[:space:]]*VERSION[[:space:]]*=' "$f" | head -1 || true)"
|
|
|
|
|
|
|
|
[[ -n "$line" ]] || { echo ""; return 1; }
|
|
|
|
|
|
|
|
# strip up to '='
|
|
|
|
|
|
|
|
line="${line#*=}"
|
|
|
|
|
|
|
|
# trim spaces
|
|
|
|
|
|
|
|
line="$(echo "$line" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')"
|
|
|
|
|
|
|
|
# drop trailing comments
|
|
|
|
|
|
|
|
line="${line%%#*}"
|
|
|
|
|
|
|
|
# strip quotes
|
|
|
|
|
|
|
|
line="${line%\"}"; line="${line#\"}"
|
|
|
|
|
|
|
|
line="${line%\'}"; line="${line#\'}"
|
|
|
|
|
|
|
|
# keep only semver-ish chars
|
|
|
|
|
|
|
|
line="$(echo "$line" | sed -E 's/[^0-9A-Za-z\.\-\+].*$//')"
|
|
|
|
|
|
|
|
echo "$line"
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
compare_versions() {
|
|
|
|
|
|
|
|
# Compare two dotted numeric versions: echoes 1 if $1 > $2, -1 if $1 < $2, 0 if equal.
|
|
|
|
|
|
|
|
# Non-numeric segments beyond dots are ignored (basic semver compare).
|
|
|
|
|
|
|
|
local a="$1" b="$2"
|
|
|
|
|
|
|
|
# fallback: if either empty, consider different
|
|
|
|
|
|
|
|
[[ -n "$a" && -n "$b" ]] || { [[ "$a" == "$b" ]] && echo 0 || echo 1; return; }
|
|
|
|
|
|
|
|
IFS=. read -r -a av <<< "$a"
|
|
|
|
|
|
|
|
IFS=. read -r -a bv <<< "$b"
|
|
|
|
|
|
|
|
local len="${#av[@]}"
|
|
|
|
|
|
|
|
(( ${#bv[@]} > len )) && len="${#bv[@]}"
|
|
|
|
|
|
|
|
local i ai bi
|
|
|
|
|
|
|
|
for ((i=0;i<len;i++)); do
|
|
|
|
|
|
|
|
ai="${av[i]:-0}"; bi="${bv[i]:-0}"
|
|
|
|
|
|
|
|
# force base-10 (avoid octal)
|
|
|
|
|
|
|
|
if (( 10#$ai > 10#$bi )); then echo 1; return; fi
|
|
|
|
|
|
|
|
if (( 10#$ai < 10#$bi )); then echo -1; return; fi
|
|
|
|
|
|
|
|
done
|
|
|
|
|
|
|
|
echo 0
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- parse options ------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
|
|
case "$1" in
|
|
|
|
case "$1" in
|
|
|
|
-s|--system)
|
|
|
|
-s|--system)
|
|
|
|
@ -68,13 +121,13 @@ done
|
|
|
|
|
|
|
|
|
|
|
|
DEST_PATH="$DEST_DIR/dsync"
|
|
|
|
DEST_PATH="$DEST_DIR/dsync"
|
|
|
|
|
|
|
|
|
|
|
|
# create temporary dir and ensure cleanup
|
|
|
|
# --- prepare dirs -------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
TMPDIR="$(mktemp -d)"
|
|
|
|
TMPDIR="$(mktemp -d)"
|
|
|
|
trap 'rm -rf "$TMPDIR"' EXIT
|
|
|
|
trap 'rm -rf "$TMPDIR"' EXIT
|
|
|
|
|
|
|
|
|
|
|
|
echo "Installing dsync to: $DEST_DIR"
|
|
|
|
echo "Installing dsync to: $DEST_DIR"
|
|
|
|
|
|
|
|
|
|
|
|
# ensure target directory exists (create with sudo if system dir)
|
|
|
|
|
|
|
|
if [[ ! -d "$DEST_DIR" ]]; then
|
|
|
|
if [[ ! -d "$DEST_DIR" ]]; then
|
|
|
|
echo "Target directory $DEST_DIR does not exist — attempting to create it..."
|
|
|
|
echo "Target directory $DEST_DIR does not exist — attempting to create it..."
|
|
|
|
if ! mkdir -p "$DEST_DIR" 2>/dev/null; then
|
|
|
|
if ! mkdir -p "$DEST_DIR" 2>/dev/null; then
|
|
|
|
@ -89,29 +142,30 @@ fi
|
|
|
|
|
|
|
|
|
|
|
|
FOUND=""
|
|
|
|
FOUND=""
|
|
|
|
|
|
|
|
|
|
|
|
# 1) Try to clone repo with git (if git available)
|
|
|
|
# --- fetch from git (preferred) ----------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
if command -v git >/dev/null 2>&1; then
|
|
|
|
if command -v git >/dev/null 2>&1; then
|
|
|
|
echo "git found — attempting shallow clone..."
|
|
|
|
echo "git found — attempting shallow clone..."
|
|
|
|
if git clone --depth 1 "$REPO_URL" "$TMPDIR/repo" >/dev/null 2>&1; then
|
|
|
|
if git clone --quiet --depth 1 "$REPO_URL" "$TMPDIR/repo" >/dev/null 2>&1; then
|
|
|
|
echo "Repository cloned."
|
|
|
|
echo "Repository cloned."
|
|
|
|
else
|
|
|
|
else
|
|
|
|
echo "Shallow clone failed, attempting normal clone (may show output)..."
|
|
|
|
echo "Shallow clone failed, attempting normal clone (may show output)..."
|
|
|
|
git clone --depth 1 "$REPO_URL" "$TMPDIR/repo" || true
|
|
|
|
git clone --depth 1 "$REPO_URL" "$TMPDIR/repo" || true
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [[ -d "$TMPDIR/repo" ]]; then
|
|
|
|
if [[ -d "$TMPDIR/repo" ]]; then
|
|
|
|
|
|
|
|
# try to locate the dsync file
|
|
|
|
FOUND="$(find "$TMPDIR/repo" -maxdepth 6 -type f -name dsync -print -quit || true)"
|
|
|
|
FOUND="$(find "$TMPDIR/repo" -maxdepth 6 -type f -name dsync -print -quit || true)"
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# 2) If git did not find the file, try to download raw file via curl (main/master)
|
|
|
|
# --- fallback: raw download from main/master ---------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
if [[ -z "$FOUND" ]]; then
|
|
|
|
if [[ -z "$FOUND" ]]; then
|
|
|
|
if command -v curl >/dev/null 2>&1; then
|
|
|
|
if command -v curl >/dev/null 2>&1; then
|
|
|
|
for BR in main master; do
|
|
|
|
for BR in main master; do
|
|
|
|
RAW_URL="$RAW_BASE/$BR/dsync"
|
|
|
|
RAW_URL="$RAW_BASE/$BR/dsync"
|
|
|
|
echo "Trying to download raw file: $RAW_URL"
|
|
|
|
echo "Trying to download raw file: $RAW_URL"
|
|
|
|
if curl --fail -L --max-redirs 5 -o "$TMPDIR/dsync" "$RAW_URL"; then
|
|
|
|
if curl --fail -L --max-redirs 5 -o "$TMPDIR/dsync" "$RAW_URL"; then
|
|
|
|
# check that downloaded file looks like text/script, not HTML error
|
|
|
|
|
|
|
|
if file "$TMPDIR/dsync" | grep -qE 'text|python|ASCII'; then
|
|
|
|
if file "$TMPDIR/dsync" | grep -qE 'text|python|ASCII'; then
|
|
|
|
FOUND="$TMPDIR/dsync"
|
|
|
|
FOUND="$TMPDIR/dsync"
|
|
|
|
break
|
|
|
|
break
|
|
|
|
@ -136,13 +190,47 @@ fi
|
|
|
|
|
|
|
|
|
|
|
|
echo "Found source file at: $FOUND"
|
|
|
|
echo "Found source file at: $FOUND"
|
|
|
|
|
|
|
|
|
|
|
|
# Warn if target exists
|
|
|
|
# --- version comparison logic -------------------------------------------------
|
|
|
|
if [[ -e "$DEST_PATH" && "$FORCE" -ne 1 ]]; then
|
|
|
|
|
|
|
|
echo "Error: $DEST_PATH already exists. Use --force to overwrite." >&2
|
|
|
|
REMOTE_VER="$(extract_version "$FOUND" || true)"
|
|
|
|
exit 1
|
|
|
|
LOCAL_VER=""
|
|
|
|
|
|
|
|
if [[ -e "$DEST_PATH" ]]; then
|
|
|
|
|
|
|
|
LOCAL_VER="$(extract_version "$DEST_PATH" || true)"
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# copy into destination (use sudo if target dir is not writable)
|
|
|
|
if [[ "$FORCE" -eq 0 && -e "$DEST_PATH" ]]; then
|
|
|
|
|
|
|
|
if [[ -n "$REMOTE_VER" && -n "$LOCAL_VER" ]]; then
|
|
|
|
|
|
|
|
CMP="$(compare_versions "$REMOTE_VER" "$LOCAL_VER")"
|
|
|
|
|
|
|
|
if [[ "$CMP" -eq 0 ]]; then
|
|
|
|
|
|
|
|
echo "dsync is already up to date (version $LOCAL_VER). Use --force to reinstall."
|
|
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
|
|
elif [[ "$CMP" -lt 0 ]]; then
|
|
|
|
|
|
|
|
echo "Installed dsync ($LOCAL_VER) is newer than remote ($REMOTE_VER)."
|
|
|
|
|
|
|
|
echo "Use --force to downgrade or reinstall."
|
|
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
echo "Updating dsync: $LOCAL_VER -> $REMOTE_VER"
|
|
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
echo "Version check inconclusive (remote='$REMOTE_VER', local='$LOCAL_VER')."
|
|
|
|
|
|
|
|
echo "Proceeding with install (use --force to bypass version checks explicitly)."
|
|
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
elif [[ -e "$DEST_PATH" && "$FORCE" -eq 1 ]]; then
|
|
|
|
|
|
|
|
if [[ -n "$REMOTE_VER" && -n "$LOCAL_VER" ]]; then
|
|
|
|
|
|
|
|
echo "Reinstalling (forced): $LOCAL_VER -> $REMOTE_VER"
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
echo "Reinstalling (forced)."
|
|
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
if [[ -n "$REMOTE_VER" ]]; then
|
|
|
|
|
|
|
|
echo "Installing dsync version $REMOTE_VER"
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
echo "Installing dsync (version unknown)"
|
|
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- install/copy -------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
if touch "$DEST_DIR/.write_test" >/dev/null 2>&1; then
|
|
|
|
if touch "$DEST_DIR/.write_test" >/dev/null 2>&1; then
|
|
|
|
rm -f "$DEST_DIR/.write_test"
|
|
|
|
rm -f "$DEST_DIR/.write_test"
|
|
|
|
cp -f "$FOUND" "$DEST_PATH"
|
|
|
|
cp -f "$FOUND" "$DEST_PATH"
|
|
|
|
@ -159,7 +247,8 @@ else
|
|
|
|
echo "Installed (via sudo): $DEST_PATH"
|
|
|
|
echo "Installed (via sudo): $DEST_PATH"
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# quick PATH check
|
|
|
|
# --- final PATH hint ----------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
echo
|
|
|
|
echo
|
|
|
|
echo "PATH check:"
|
|
|
|
echo "PATH check:"
|
|
|
|
if command -v dsync >/dev/null 2>&1; then
|
|
|
|
if command -v dsync >/dev/null 2>&1; then
|
|
|
|
|