From 52f28d517b226aff34084769012ca5aab5722089 Mon Sep 17 00:00:00 2001 From: Julien Gautier Date: Wed, 29 Apr 2026 01:50:26 +0200 Subject: [PATCH 1/3] dirsync initial commit --- dirsync/README.md | 75 +++++++++++++++++++++ dirsync/dir-sync.sh | 160 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 dirsync/README.md create mode 100755 dirsync/dir-sync.sh diff --git a/dirsync/README.md b/dirsync/README.md new file mode 100644 index 0000000..c57a4ec --- /dev/null +++ b/dirsync/README.md @@ -0,0 +1,75 @@ +# Directory Sync Utility + +This directory contains a tool for syncing a local directory to a remote host in real time. +It watches for file changes and transfers them immediately via SCP, making it useful for +live development on a remote server. + +## Scripts + +| Script | Description | +|---|---| +| `dir-sync.sh` | Improved version — recommended | +| `scpsync.sh` | Original version | + +## Quick Start + +```bash +./dir-sync.sh -s /path/to/local/dir/ -d /path/to/remote/dir/ -r sshhost +``` + +The script starts watching the local directory immediately. Press **Ctrl+C** to stop — +it will reset the remote repository (`git reset --hard`) and clear the remote cache before exiting. + +## Options + +| Option | Description | +|---|---| +| `-s, --source ` | Local directory absolute path | +| `-d, --dest ` | Remote directory absolute path | +| `-r, --remote-host ` | Remote SSH host (as configured in `~/.ssh/config`) | +| `-h, --help` | Print help | + +If an option is omitted, the script falls back to the default values defined at the top of the script. + +## How It Works + +1. **File watching**: `fswatch` monitors the source directory for any change, excluding `.git/` +2. **Batch end marker**: `fswatch --batch-marker` emits a `NoOp` signal at the end of each batch +3. **File sync**: each changed file is transferred with `scp`, preserving the relative path under the destination directory +4. **Cache clear**: on each `NoOp` (end of batch), `composer front-cache-clear` is run on the remote +5. **Cleanup on exit**: Ctrl+C triggers `git reset --hard` and a final cache clear on the remote + +## Default Configuration + +Edit the three constants at the top of `dir-sync.sh` to set your own defaults: + +```bash +DEFAULT_LOCAL_PATH='/path/to/local/dir/' +DEFAULT_REMOTE_PATH='/path/to/remote/dir/' +DEFAULT_HOST='devac' +``` + +## Requirements + +- [`fswatch`](https://github.com/emcrisostomo/fswatch) — file system watcher +- `scp` and `ssh` — standard OpenSSH tools +- A remote host configured in `~/.ssh/config` (password-less key auth recommended) +- `composer` available on the remote host (for cache clearing) + +### Installing fswatch + +```bash +# macOS +brew install fswatch + +# Linux (Ubuntu/Debian) +sudo apt install fswatch +``` + +## About + +**Author:** Julien Gautier ([jgautier.webdev@gmail.com](mailto:jgautier.webdev@gmail.com)) + +**Organization:** AdAstra + +**Project:** adastra_scripts diff --git a/dirsync/dir-sync.sh b/dirsync/dir-sync.sh new file mode 100755 index 0000000..3be5637 --- /dev/null +++ b/dirsync/dir-sync.sh @@ -0,0 +1,160 @@ +#!/bin/zsh + +RETVAL=0 + +# CONF +defaultLocalPath='/path/to/local/dir/' +defaultRemotePath='/path/to/remote/dir/' +defaultHost='devac' + +NONE='\033[0m' # none +GREY='\033[2m' # grey +BOLD='\033[1m' # bold + +GREEN='\033[0;32m' # green +YELLOW='\033[0;33m' # yellow +RED='\033[0;31m' # red +BLUE='\033[0;34m' # blue +CYAN='\033[0;36m' # cyan +MAGENTA='\033[0;35m' # magenta +BLACK='\033[0;30m' # black +WHITE='\033[0;37m' # white + +BBLUE='\033[1;94m' # blue bold + +POSITIONAL=() + +clear + +echo -e "${NONE}${BBLUE}🔎 Lancement du script $0 ${NONE}${GREY}" +echo -e "${NONE}${BLUE}-------------------------------${NONE}${GREY}" + +Usage() +{ + echo -e "${BOLD}NAME${NONE}" + echo " scpsync - Synchronize local folder to a remote host" + echo " " + echo -e "${BOLD}SYNOPSIS${NONE}" + echo " sh $0 [OPTION]..." + echo " " + echo -e "${BOLD}DESCRIPTION${NONE}" + echo " -h, --help" + echo " " + echo " print this help" + echo " " + echo " -d, --dest" + echo " " + echo " remote directory absolut path" + echo " " + echo " -s, --source" + echo " " + echo " source directory absolut path" + echo " " + echo " -r, --remote-host" + echo " remote ssh host" + echo " " + echo -e "${RED} 🐞${NONE} Usage: $0 -s /path/to/local/dir/ -d /path/to/remote/dir/ -r sshhost" +} + +while [[ $# -gt 0 ]] +do + key="$1" + case $key in + -h|--help) + Usage + exit 0 + ;; + -d|--dest) + dirRemotePath=$2 + shift # past argument + shift # past value + ;; + -s|--source) + dirLocalPath=$2 + shift + shift + ;; + -r|--remote-host) + host=$2 + shift + shift + ;; + *) + # unknown option + POSITIONAL+=("$1") # save it in an array for later + shift + ;; + esac +done + +set -- "${POSITIONAL[@]}" + +if test -z "$dirLocalPath"; then + dirLocalPath=$defaultLocalPath + echo -e "${YELLOW}⚠ Local path:${NONE} ${dirLocalPath}${YELLOW} (default)${NONE}" +else + echo -e "${GREEN}⚙️ Local path:${NONE} ${dirLocalPath}" +fi + +if test -z "$dirRemotePath"; then + dirRemotePath=$defaultRemotePath + echo -e "${YELLOW}⚠ Remote path:${NONE} ${dirRemotePath}${YELLOW} (default)${NONE}" +else + echo -e "${GREEN}⚙️ Remote path:${NONE} ${dirRemotePath}" +fi + +if [ -z "$host" ]; then + host=$defaultHost + echo -e "${YELLOW}⚠ Remote host:${NONE} ${host}${YELLOW} (default)${NONE}" +else + echo -e "${GREEN}⚙️ Remote host:${NONE} ${host}" +fi + +if [ $RETVAL -eq 0 ]; then + trap exit INT + function exit() { + echo -e "" + echo -e "${NONE}${CYAN}♻️ Réinitialisation du répo distant...${NONE}${GREY}" + echo -e "${NONE}\$> ${MAGENTA}ssh ${host} \"cd ${dirRemotePath}; git reset --hard\"${NONE}" + ssh $host "cd $dirRemotePath; git reset --hard" + + echo -e "\n${NONE}${CYAN}🗑️ Suppression du cache${NONE}${GREY}" + echo -e "${NONE}\$> ${MAGENTA}ssh ${host} \"cd ${dirRemotePath}; composer front-cache-clear\"${NONE}" + ssh $host "cd $dirRemotePath; composer front-cache-clear" + + echo -e "${NONE}${GREEN}✔️ Ciao !${NONE}" + } + + syncfile () { + fileLocalPath=$1 + fileRemotePath=${fileLocalPath/$dirLocalPath/$dirRemotePath} + echo -e "\n${NONE}${CYAN}🔄 Synchronisation du fichier ${YELLOW}${fileLocalPath/$dirLocalPath/}${NONE}" + echo -e "${NONE}\$> ${MAGENTA}scp ${fileLocalPath} ${host}:${fileRemotePath}${NONE}" + scp ${fileLocalPath} $host:$fileRemotePath + } + + echo -e "\n${NONE}${BBLUE}⏳ En attente de modification${NONE}${GREY}" + echo -e "${NONE}${BLUE}-----------------------------${NONE}${GREY}" + + num=0 + while read fileLocalPath; do + if [ "$fileLocalPath" = "NoOp" ]; then + num=0 + echo -e "\n${NONE}${CYAN}🗑️ Suppression du cache${NONE}${GREY}" + echo -e "${NONE}\$> ${MAGENTA}ssh ${host} \"cd ${dirRemotePath}; composer front-cache-clear\"${NONE}\n" + ssh $host "cd $dirRemotePath; composer front-cache-clear" + echo -e "\n${NONE}${BBLUE}⏳ En attente de modification${NONE}${GREY}" + echo -e "${NONE}${BLUE}-----------------------------${NONE}${GREY}" + else + num=$((num+1)) + #echo -e "${NONE}${num} - ${fileLocalPath}" + syncfile $fileLocalPath $num & + fi + done < <(fswatch --batch-marker -0 --exclude '.git/' $dirLocalPath | xargs -0 -n 1 -I {}) + +else + echo -e "${RED}⚠ ${NONE} Le script a rencontré une erreur" + echo "-----------------------------" + echo -e "${RED} 💀 Exit code:${NONE} $RETVAL" + exit $RETVAL +fi From c81abeda54e14074c9b77ac7b4674de2dda62277 Mon Sep 17 00:00:00 2001 From: Julien Gautier Date: Wed, 29 Apr 2026 01:57:17 +0200 Subject: [PATCH 2/3] chore: use #!/usr/bin/env bash in all scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More portable than #!/bin/bash — resolves bash from PATH, which handles installations outside /bin (e.g. Homebrew on macOS). --- dbdump_mongo/db-dump.sh | 2 +- dbrestore_mongo/db-restore.sh | 2 +- dbutils_mongo/db-start.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dbdump_mongo/db-dump.sh b/dbdump_mongo/db-dump.sh index 0755581..3e2dfb9 100755 --- a/dbdump_mongo/db-dump.sh +++ b/dbdump_mongo/db-dump.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Author: Julien Gautier # Organization: AdAstra diff --git a/dbrestore_mongo/db-restore.sh b/dbrestore_mongo/db-restore.sh index 2018ad9..618b02e 100755 --- a/dbrestore_mongo/db-restore.sh +++ b/dbrestore_mongo/db-restore.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Author: Julien Gautier # Organization: AdAstra diff --git a/dbutils_mongo/db-start.sh b/dbutils_mongo/db-start.sh index 0de319e..29dfd9a 100755 --- a/dbutils_mongo/db-start.sh +++ b/dbutils_mongo/db-start.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Author: Julien Gautier # Organization: AdAstra From 6a87248cc060615c629238bbd1647ec827023769 Mon Sep 17 00:00:00 2001 From: Julien Gautier Date: Wed, 29 Apr 2026 01:59:10 +0200 Subject: [PATCH 3/3] refactor(dir-sync): script refactoring and bug fix --- dirsync/dir-sync.sh | 274 ++++++++++++++++++++++---------------------- 1 file changed, 139 insertions(+), 135 deletions(-) diff --git a/dirsync/dir-sync.sh b/dirsync/dir-sync.sh index 3be5637..a9d55ed 100755 --- a/dirsync/dir-sync.sh +++ b/dirsync/dir-sync.sh @@ -1,160 +1,164 @@ -#!/bin/zsh +#!/usr/bin/env bash -RETVAL=0 +# Author: Julien Gautier +# Organization: AdAstra +# Project: adastra_scripts +# +################################################################################ +# Directory Sync Utility Script +# +# Watches a local directory for file changes and syncs them to a remote host +# via SCP in real time. On exit (Ctrl+C), resets the remote repository and +# clears the remote cache. +# +# Usage: ./dir-sync.sh [OPTIONS] +# +# Options: +# -s, --source Local directory absolute path +# -d, --dest Remote directory absolute path +# -r, --remote-host Remote SSH host +# -h, --help Print this help +# +# Examples: +# ./dir-sync.sh -s /var/www/myapp/ -d /var/www/myapp/ -r myserver +# ./dir-sync.sh -s ~/projects/app/ -d /srv/app/ -r devhost +# +# Requirements: +# fswatch, scp, ssh +# +################################################################################ -# CONF -defaultLocalPath='/path/to/local/dir/' -defaultRemotePath='/path/to/remote/dir/' -defaultHost='devac' +# --- Configuration ----------------------------------------------------------- +DEFAULT_LOCAL_PATH='/path/to/local/dir/' +DEFAULT_REMOTE_PATH='/path/to/remote/dir/' +DEFAULT_HOST='devhost' -NONE='\033[0m' # none -GREY='\033[2m' # grey -BOLD='\033[1m' # bold +# --- Colors ------------------------------------------------------------------ +NONE='\033[0m' +BOLD='\033[1m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +RED='\033[0;31m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +MAGENTA='\033[0;35m' +BBLUE='\033[1;94m' -GREEN='\033[0;32m' # green -YELLOW='\033[0;33m' # yellow -RED='\033[0;31m' # red -BLUE='\033[0;34m' # blue -CYAN='\033[0;36m' # cyan -MAGENTA='\033[0;35m' # magenta -BLACK='\033[0;30m' # black -WHITE='\033[0;37m' # white +# --- Usage ------------------------------------------------------------------- +usage() { + echo -e " +${BOLD}NAME${NONE} + dir-sync — Synchronize a local folder to a remote host -BBLUE='\033[1;94m' # blue bold +${BOLD}SYNOPSIS${NONE} + bash $0 [OPTION]... -POSITIONAL=() +${BOLD}OPTIONS${NONE} + -h, --help Print this help + -s, --source Local directory absolute path + -d, --dest Remote directory absolute path + -r, --remote-host Remote SSH host -clear - -echo -e "${NONE}${BBLUE}🔎 Lancement du script $0 ${NONE}${GREY}" -echo -e "${NONE}${BLUE}-------------------------------${NONE}${GREY}" - -Usage() -{ - echo -e "${BOLD}NAME${NONE}" - echo " scpsync - Synchronize local folder to a remote host" - echo " " - echo -e "${BOLD}SYNOPSIS${NONE}" - echo " sh $0 [OPTION]..." - echo " " - echo -e "${BOLD}DESCRIPTION${NONE}" - echo " -h, --help" - echo " " - echo " print this help" - echo " " - echo " -d, --dest" - echo " " - echo " remote directory absolut path" - echo " " - echo " -s, --source" - echo " " - echo " source directory absolut path" - echo " " - echo " -r, --remote-host" - echo " remote ssh host" - echo " " - echo -e "${RED} 🐞${NONE} Usage: $0 -s /path/to/local/dir/ -d /path/to/remote/dir/ -r sshhost" +${RED}Example:${NONE} $0 -s /path/to/local/ -d /path/to/remote/ -r sshhost +" } -while [[ $# -gt 0 ]] -do - key="$1" - case $key in - -h|--help) - Usage - exit 0 - ;; - -d|--dest) - dirRemotePath=$2 - shift # past argument - shift # past value - ;; - -s|--source) - dirLocalPath=$2 - shift - shift - ;; - -r|--remote-host) - host=$2 - shift - shift - ;; +# --- Prerequisite checks ----------------------------------------------------- +check_deps() { + local missing=0 + for cmd in fswatch scp ssh; do + if ! command -v "$cmd" &>/dev/null; then + echo -e "${RED}✗ Missing required command: ${cmd}${NONE}" + missing=1 + fi + done + [ "$missing" -eq 0 ] || exit 1 +} + +# --- Cleanup (runs on INT/TERM) ---------------------------------------------- +cleanup() { + echo -e "\n${CYAN}♻️ Resetting remote repository...${NONE}" + echo -e "\$> ${MAGENTA}ssh ${host} \"cd '${dir_remote}'; git reset --hard\"${NONE}" + ssh "$host" "cd '${dir_remote}'; git reset --hard" + + echo -e "\n${CYAN}🗑️ Clearing remote cache...${NONE}" + echo -e "\$> ${MAGENTA}ssh ${host} \"cd '${dir_remote}'; composer front-cache-clear\"${NONE}" + ssh "$host" "cd '${dir_remote}'; composer front-cache-clear" + + wait + echo -e "\n${GREEN}✔ Done.${NONE}" + exit 0 +} + +# --- Sync a single file ------------------------------------------------------ +sync_file() { + local local_path="$1" + local remote_path="${local_path/$dir_local/$dir_remote}" + echo -e "\n${CYAN}🔄 Syncing ${YELLOW}${local_path/$dir_local/}${NONE}" + echo -e "\$> ${MAGENTA}scp '${local_path}' ${host}:'${remote_path}'${NONE}" + scp "${local_path}" "${host}:${remote_path}" +} + +# --- Main -------------------------------------------------------------------- +clear + +echo -e "${BBLUE}🔎 Starting $0${NONE}" +echo -e "${BLUE}-------------------------------${NONE}" + +check_deps + +dir_local='' +dir_remote='' +host='' + +while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) usage; exit 0 ;; + -s|--source) dir_local="$2"; shift 2 ;; + -d|--dest) dir_remote="$2"; shift 2 ;; + -r|--remote-host) host="$2"; shift 2 ;; *) - # unknown option - POSITIONAL+=("$1") # save it in an array for later - shift + echo -e "${RED}✗ Unknown option: $1${NONE}" + usage + exit 1 ;; esac done -set -- "${POSITIONAL[@]}" - -if test -z "$dirLocalPath"; then - dirLocalPath=$defaultLocalPath - echo -e "${YELLOW}⚠ Local path:${NONE} ${dirLocalPath}${YELLOW} (default)${NONE}" +if [[ -z "$dir_local" ]]; then + dir_local="$DEFAULT_LOCAL_PATH" + echo -e "${YELLOW}⚠ Local path: ${dir_local} (default)${NONE}" else - echo -e "${GREEN}⚙️ Local path:${NONE} ${dirLocalPath}" + echo -e "${GREEN}⚙️ Local path: ${dir_local}${NONE}" fi -if test -z "$dirRemotePath"; then - dirRemotePath=$defaultRemotePath - echo -e "${YELLOW}⚠ Remote path:${NONE} ${dirRemotePath}${YELLOW} (default)${NONE}" +if [[ -z "$dir_remote" ]]; then + dir_remote="$DEFAULT_REMOTE_PATH" + echo -e "${YELLOW}⚠ Remote path: ${dir_remote} (default)${NONE}" else - echo -e "${GREEN}⚙️ Remote path:${NONE} ${dirRemotePath}" + echo -e "${GREEN}⚙️ Remote path: ${dir_remote}${NONE}" fi -if [ -z "$host" ]; then - host=$defaultHost - echo -e "${YELLOW}⚠ Remote host:${NONE} ${host}${YELLOW} (default)${NONE}" +if [[ -z "$host" ]]; then + host="$DEFAULT_HOST" + echo -e "${YELLOW}⚠ Remote host: ${host} (default)${NONE}" else - echo -e "${GREEN}⚙️ Remote host:${NONE} ${host}" + echo -e "${GREEN}⚙️ Remote host: ${host}${NONE}" fi -if [ $RETVAL -eq 0 ]; then - trap exit INT - function exit() { - echo -e "" - echo -e "${NONE}${CYAN}♻️ Réinitialisation du répo distant...${NONE}${GREY}" - echo -e "${NONE}\$> ${MAGENTA}ssh ${host} \"cd ${dirRemotePath}; git reset --hard\"${NONE}" - ssh $host "cd $dirRemotePath; git reset --hard" +trap cleanup INT TERM - echo -e "\n${NONE}${CYAN}🗑️ Suppression du cache${NONE}${GREY}" - echo -e "${NONE}\$> ${MAGENTA}ssh ${host} \"cd ${dirRemotePath}; composer front-cache-clear\"${NONE}" - ssh $host "cd $dirRemotePath; composer front-cache-clear" +echo -e "\n${BBLUE}⏳ Watching for changes...${NONE}" +echo -e "${BLUE}-----------------------------${NONE}" - echo -e "${NONE}${GREEN}✔️ Ciao !${NONE}" - } - - syncfile () { - fileLocalPath=$1 - fileRemotePath=${fileLocalPath/$dirLocalPath/$dirRemotePath} - echo -e "\n${NONE}${CYAN}🔄 Synchronisation du fichier ${YELLOW}${fileLocalPath/$dirLocalPath/}${NONE}" - echo -e "${NONE}\$> ${MAGENTA}scp ${fileLocalPath} ${host}:${fileRemotePath}${NONE}" - scp ${fileLocalPath} $host:$fileRemotePath - } - - echo -e "\n${NONE}${BBLUE}⏳ En attente de modification${NONE}${GREY}" - echo -e "${NONE}${BLUE}-----------------------------${NONE}${GREY}" - - num=0 - while read fileLocalPath; do - if [ "$fileLocalPath" = "NoOp" ]; then - num=0 - echo -e "\n${NONE}${CYAN}🗑️ Suppression du cache${NONE}${GREY}" - echo -e "${NONE}\$> ${MAGENTA}ssh ${host} \"cd ${dirRemotePath}; composer front-cache-clear\"${NONE}\n" - ssh $host "cd $dirRemotePath; composer front-cache-clear" - echo -e "\n${NONE}${BBLUE}⏳ En attente de modification${NONE}${GREY}" - echo -e "${NONE}${BLUE}-----------------------------${NONE}${GREY}" - else - num=$((num+1)) - #echo -e "${NONE}${num} - ${fileLocalPath}" - syncfile $fileLocalPath $num & - fi - done < <(fswatch --batch-marker -0 --exclude '.git/' $dirLocalPath | xargs -0 -n 1 -I {}) - -else - echo -e "${RED}⚠ ${NONE} Le script a rencontré une erreur" - echo "-----------------------------" - echo -e "${RED} 💀 Exit code:${NONE} $RETVAL" - exit $RETVAL -fi +while IFS= read -r file; do + if [[ "$file" == "NoOp" ]]; then + echo -e "\n${CYAN}🗑️ Clearing remote cache...${NONE}" + echo -e "\$> ${MAGENTA}ssh ${host} \"cd '${dir_remote}'; composer front-cache-clear\"${NONE}" + ssh "$host" "cd '${dir_remote}'; composer front-cache-clear" + echo -e "\n${BBLUE}⏳ Watching for changes...${NONE}" + echo -e "${BLUE}-----------------------------${NONE}" + else + sync_file "$file" & + fi +done < <(fswatch --batch-marker -0 --exclude '.git/' "$dir_local" | tr '\0' '\n')