#!/usr/bin/env bash # 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: # ./install_dsync.sh # install to ~/.local/bin (default) # ./install_dsync.sh --system # install to /usr/local/bin (requires sudo) # ./install_dsync.sh -d DIR # install to custom DIR # ./install_dsync.sh -f # force overwrite (also allows downgrade/reinstall) # ./install_dsync.sh -h # show help set -euo pipefail REPO_URL="https://git.ai.infran.ru/ilyukhin/dsync.git" RAW_BASE="https://git.ai.infran.ru/ilyukhin/dsync/-/raw" # raw URL pattern: $RAW_BASE//dsync DEFAULT_USER_DIR="$HOME/.local/bin" DEST_DIR="$DEFAULT_USER_DIR" FORCE=0 print_help() { cat < $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 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 case "$1" in -s|--system) DEST_DIR="/usr/local/bin" shift ;; -d|--dest) if [[ -z "${2:-}" ]]; then echo "Error: missing argument for $1" >&2 exit 2 fi DEST_DIR="$2" shift 2 ;; -f|--force) FORCE=1 shift ;; -h|--help) print_help exit 0 ;; --) shift break ;; -*) echo "Unknown option: $1" >&2 print_help exit 2 ;; *) break ;; esac done DEST_PATH="$DEST_DIR/dsync" # --- prepare dirs ------------------------------------------------------------- TMPDIR="$(mktemp -d)" trap 'rm -rf "$TMPDIR"' EXIT echo "Installing dsync to: $DEST_DIR" if [[ ! -d "$DEST_DIR" ]]; then echo "Target directory $DEST_DIR does not exist — attempting to create it..." if ! mkdir -p "$DEST_DIR" 2>/dev/null; then echo "No permission to create $DEST_DIR, trying with sudo..." if ! command -v sudo >/dev/null 2>&1; then echo "sudo not available — cannot create $DEST_DIR" >&2 exit 1 fi sudo mkdir -p "$DEST_DIR" fi fi FOUND="" # --- fetch from git (preferred) ---------------------------------------------- if command -v git >/dev/null 2>&1; then echo "git found — attempting shallow clone..." if git clone --quiet --depth 1 "$REPO_URL" "$TMPDIR/repo" >/dev/null 2>&1; then echo "Repository cloned." else echo "Shallow clone failed, attempting normal clone (may show output)..." git clone --depth 1 "$REPO_URL" "$TMPDIR/repo" || true fi if [[ -d "$TMPDIR/repo" ]]; then # try to locate the dsync file FOUND="$(find "$TMPDIR/repo" -maxdepth 6 -type f -name dsync -print -quit || true)" fi fi # --- fallback: raw download from main/master --------------------------------- if [[ -z "$FOUND" ]]; then if command -v curl >/dev/null 2>&1; then for BR in main master; do RAW_URL="$RAW_BASE/$BR/dsync" echo "Trying to download raw file: $RAW_URL" if curl --fail -L --max-redirs 5 -o "$TMPDIR/dsync" "$RAW_URL"; then if file "$TMPDIR/dsync" | grep -qE 'text|python|ASCII'; then FOUND="$TMPDIR/dsync" break else echo "Downloaded file from $RAW_URL does not look like a script - skipping." fi else echo "Failed to download $RAW_URL (404 or error)." fi done else echo "curl not installed and git did not yield a dsync file — cannot download." >&2 exit 1 fi fi if [[ -z "$FOUND" ]]; then echo "Could not locate dsync in the repository and raw download failed." echo "Repository may be private. If it's private, ensure you have access (SSH keys or credentials)." exit 1 fi echo "Found source file at: $FOUND" # --- version comparison logic ------------------------------------------------- REMOTE_VER="$(extract_version "$FOUND" || true)" LOCAL_VER="" if [[ -e "$DEST_PATH" ]]; then LOCAL_VER="$(extract_version "$DEST_PATH" || true)" fi 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 rm -f "$DEST_DIR/.write_test" cp -f "$FOUND" "$DEST_PATH" chmod +x "$DEST_PATH" echo "Installed: $DEST_PATH" else echo "No write permission to $DEST_DIR — using sudo to copy and set executable bit." if ! command -v sudo >/dev/null 2>&1; then echo "sudo is not available — cannot write to $DEST_DIR" >&2 exit 1 fi sudo cp -f "$FOUND" "$DEST_PATH" sudo chmod +x "$DEST_PATH" echo "Installed (via sudo): $DEST_PATH" fi # --- final PATH hint ---------------------------------------------------------- echo echo "PATH check:" if command -v dsync >/dev/null 2>&1; then echo "dsync is found: $(command -v dsync)" else echo "dsync was installed to $DEST_PATH but is not found in current PATH." echo "Make sure $DEST_DIR is in your PATH (add to ~/.profile, ~/.bashrc or ~/.zshrc if needed):" echo " export PATH=\"$DEST_DIR:\$PATH\"" echo "Then run 'hash -r' (bash) or 'rehash' (zsh) or restart your shell." fi echo "Done."